mirror of
https://github.com/mCaptcha/mCaptcha.git
synced 2025-03-14 13:08:27 +03:00
errorable and seperated runner methods for auth
This commit is contained in:
parent
2162d32455
commit
cc17f2048f
9 changed files with 222 additions and 135 deletions
|
@ -18,7 +18,7 @@
|
|||
use actix_identity::Identity;
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
|
||||
use super::auth::Password;
|
||||
use super::auth::runners::Password;
|
||||
use crate::errors::*;
|
||||
use crate::AppData;
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ use actix_web::test;
|
|||
|
||||
use super::email::*;
|
||||
use super::*;
|
||||
use crate::api::v1::auth::*;
|
||||
use crate::api::v1::auth::runners::Password;
|
||||
use crate::api::v1::ROUTES;
|
||||
use crate::data::Data;
|
||||
use crate::*;
|
||||
|
|
|
@ -14,12 +14,10 @@
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use std::borrow::Cow;
|
||||
|
||||
use actix_identity::Identity;
|
||||
use actix_web::http::header;
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::mcaptcha::get_random;
|
||||
|
@ -47,127 +45,145 @@ pub mod routes {
|
|||
}
|
||||
}
|
||||
|
||||
pub mod runners {
|
||||
use std::borrow::Cow;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Register {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub confirm_password: String,
|
||||
pub email: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Login {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Password {
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
/// returns Ok(()) when everything checks out and the user is authenticated. Erros otherwise
|
||||
pub async fn login_runner(payload: &Login, data: &AppData) -> ServiceResult<()> {
|
||||
use argon2_creds::Config;
|
||||
use sqlx::Error::RowNotFound;
|
||||
|
||||
let rec = sqlx::query_as!(
|
||||
Password,
|
||||
r#"SELECT password FROM mcaptcha_users WHERE name = ($1)"#,
|
||||
&payload.username,
|
||||
)
|
||||
.fetch_one(&data.db)
|
||||
.await;
|
||||
|
||||
match rec {
|
||||
Ok(s) => {
|
||||
if Config::verify(&s.password, &payload.password)? {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ServiceError::WrongPassword)
|
||||
}
|
||||
}
|
||||
Err(RowNotFound) => Err(ServiceError::UsernameNotFound),
|
||||
Err(_) => Err(ServiceError::InternalServerError),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn register_runner(
|
||||
payload: &Register,
|
||||
data: &AppData,
|
||||
) -> ServiceResult<()> {
|
||||
if !crate::SETTINGS.server.allow_registration {
|
||||
return Err(ServiceError::ClosedForRegistration);
|
||||
}
|
||||
|
||||
if payload.password != payload.confirm_password {
|
||||
return Err(ServiceError::PasswordsDontMatch);
|
||||
}
|
||||
let username = data.creds.username(&payload.username)?;
|
||||
let hash = data.creds.password(&payload.password)?;
|
||||
|
||||
if let Some(email) = &payload.email {
|
||||
data.creds.email(&email)?;
|
||||
}
|
||||
|
||||
let mut secret;
|
||||
|
||||
loop {
|
||||
secret = get_random(32);
|
||||
let res;
|
||||
if let Some(email) = &payload.email {
|
||||
res = sqlx::query!(
|
||||
"INSERT INTO mcaptcha_users
|
||||
(name , password, email, secret) VALUES ($1, $2, $3, $4)",
|
||||
&username,
|
||||
&hash,
|
||||
&email,
|
||||
&secret,
|
||||
)
|
||||
.execute(&data.db)
|
||||
.await;
|
||||
} else {
|
||||
res = sqlx::query!(
|
||||
"INSERT INTO mcaptcha_users
|
||||
(name , password, secret) VALUES ($1, $2, $3)",
|
||||
&username,
|
||||
&hash,
|
||||
&secret,
|
||||
)
|
||||
.execute(&data.db)
|
||||
.await;
|
||||
}
|
||||
if res.is_ok() {
|
||||
break;
|
||||
} else if let Err(sqlx::Error::Database(err)) = res {
|
||||
if err.code() == Some(Cow::from("23505")) {
|
||||
let msg = err.message();
|
||||
if msg.contains("mcaptcha_users_name_key") {
|
||||
return Err(ServiceError::UsernameTaken);
|
||||
} else if msg.contains("mcaptcha_users_secret_key") {
|
||||
continue;
|
||||
} else {
|
||||
return Err(ServiceError::InternalServerError);
|
||||
}
|
||||
} else {
|
||||
return Err(sqlx::Error::Database(err).into());
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(signup);
|
||||
cfg.service(signin);
|
||||
cfg.service(register);
|
||||
cfg.service(login);
|
||||
cfg.service(signout);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Register {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub confirm_password: String,
|
||||
pub email: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Login {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Password {
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[my_codegen::post(path = "crate::V1_API_ROUTES.auth.register")]
|
||||
async fn signup(
|
||||
payload: web::Json<Register>,
|
||||
async fn register(
|
||||
payload: web::Json<runners::Register>,
|
||||
data: AppData,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
if !crate::SETTINGS.server.allow_registration {
|
||||
return Err(ServiceError::ClosedForRegistration);
|
||||
}
|
||||
|
||||
if payload.password != payload.confirm_password {
|
||||
return Err(ServiceError::PasswordsDontMatch);
|
||||
}
|
||||
let username = data.creds.username(&payload.username)?;
|
||||
let hash = data.creds.password(&payload.password)?;
|
||||
|
||||
if let Some(email) = &payload.email {
|
||||
data.creds.email(&email)?;
|
||||
}
|
||||
|
||||
let mut secret;
|
||||
|
||||
loop {
|
||||
secret = get_random(32);
|
||||
let res;
|
||||
if let Some(email) = &payload.email {
|
||||
res = sqlx::query!(
|
||||
"INSERT INTO mcaptcha_users
|
||||
(name , password, email, secret) VALUES ($1, $2, $3, $4)",
|
||||
&username,
|
||||
&hash,
|
||||
&email,
|
||||
&secret,
|
||||
)
|
||||
.execute(&data.db)
|
||||
.await;
|
||||
} else {
|
||||
res = sqlx::query!(
|
||||
"INSERT INTO mcaptcha_users
|
||||
(name , password, secret) VALUES ($1, $2, $3)",
|
||||
&username,
|
||||
&hash,
|
||||
&secret,
|
||||
)
|
||||
.execute(&data.db)
|
||||
.await;
|
||||
}
|
||||
if res.is_ok() {
|
||||
break;
|
||||
} else if let Err(sqlx::Error::Database(err)) = res {
|
||||
if err.code() == Some(Cow::from("23505")) {
|
||||
let msg = err.message();
|
||||
if msg.contains("mcaptcha_users_name_key") {
|
||||
return Err(ServiceError::UsernameTaken);
|
||||
} else if msg.contains("mcaptcha_users_secret_key") {
|
||||
continue;
|
||||
} else {
|
||||
return Err(ServiceError::InternalServerError);
|
||||
}
|
||||
} else {
|
||||
return Err(sqlx::Error::Database(err).into());
|
||||
}
|
||||
};
|
||||
}
|
||||
runners::register_runner(&payload, &data).await?;
|
||||
Ok(HttpResponse::Ok())
|
||||
}
|
||||
|
||||
#[my_codegen::post(path = "crate::V1_API_ROUTES.auth.login")]
|
||||
async fn signin(
|
||||
async fn login(
|
||||
id: Identity,
|
||||
payload: web::Json<Login>,
|
||||
payload: web::Json<runners::Login>,
|
||||
data: AppData,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
use argon2_creds::Config;
|
||||
use sqlx::Error::RowNotFound;
|
||||
|
||||
let rec = sqlx::query_as!(
|
||||
Password,
|
||||
r#"SELECT password FROM mcaptcha_users WHERE name = ($1)"#,
|
||||
&payload.username,
|
||||
)
|
||||
.fetch_one(&data.db)
|
||||
.await;
|
||||
|
||||
match rec {
|
||||
Ok(s) => {
|
||||
if Config::verify(&s.password, &payload.password)? {
|
||||
debug!("remembered {}", payload.username);
|
||||
id.remember(payload.into_inner().username);
|
||||
Ok(HttpResponse::Ok())
|
||||
} else {
|
||||
Err(ServiceError::WrongPassword)
|
||||
}
|
||||
}
|
||||
Err(RowNotFound) => Err(ServiceError::UsernameNotFound),
|
||||
Err(_) => Err(ServiceError::InternalServerError),
|
||||
}
|
||||
runners::login_runner(&payload, &data).await?;
|
||||
id.remember(payload.into_inner().username);
|
||||
Ok(HttpResponse::Ok())
|
||||
}
|
||||
|
||||
#[my_codegen::get(path = "crate::V1_API_ROUTES.auth.logout", wrap = "crate::CheckLogin")]
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
use actix_web::http::{header, StatusCode};
|
||||
use actix_web::test;
|
||||
|
||||
use crate::api::v1::auth::*;
|
||||
use crate::api::v1::auth::runners::{Login, Register};
|
||||
use crate::api::v1::ROUTES;
|
||||
use crate::data::Data;
|
||||
use crate::errors::*;
|
||||
|
@ -57,17 +57,6 @@ async fn auth_works() {
|
|||
let (_, _, signin_resp) = register_and_signin(NAME, EMAIL, PASSWORD).await;
|
||||
let cookies = get_cookie!(signin_resp);
|
||||
|
||||
// // check if update user secret works
|
||||
// let resp = test::call_service(
|
||||
// &mut app,
|
||||
// test::TestRequest::post()
|
||||
// .cookie(cookies.clone())
|
||||
// .uri(GET_SECRET)
|
||||
// .to_request(),
|
||||
// )
|
||||
// .await;
|
||||
// assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
// 2. check if duplicate username is allowed
|
||||
let msg = Register {
|
||||
username: NAME.into(),
|
||||
|
@ -86,7 +75,7 @@ async fn auth_works() {
|
|||
.await;
|
||||
|
||||
// 3. sigining in with non-existent user
|
||||
let mut login = Login {
|
||||
let mut creds = Login {
|
||||
username: "nonexistantuser".into(),
|
||||
password: msg.password.clone(),
|
||||
};
|
||||
|
@ -94,21 +83,36 @@ async fn auth_works() {
|
|||
NAME,
|
||||
PASSWORD,
|
||||
ROUTES.auth.login,
|
||||
&login,
|
||||
&creds,
|
||||
ServiceError::UsernameNotFound,
|
||||
StatusCode::NOT_FOUND,
|
||||
)
|
||||
.await;
|
||||
|
||||
let resp = test::call_service(
|
||||
&mut app,
|
||||
post_request!(&creds, PAGES.auth.login).to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
|
||||
creds.username = NAME.into();
|
||||
let resp = test::call_service(
|
||||
&mut app,
|
||||
post_request!(&creds, PAGES.auth.login).to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
// 4. trying to signin with wrong password
|
||||
login.username = NAME.into();
|
||||
login.password = NAME.into();
|
||||
creds.username = NAME.into();
|
||||
creds.password = NAME.into();
|
||||
|
||||
bad_post_req_test(
|
||||
NAME,
|
||||
PASSWORD,
|
||||
ROUTES.auth.login,
|
||||
&login,
|
||||
&creds,
|
||||
ServiceError::WrongPassword,
|
||||
StatusCode::UNAUTHORIZED,
|
||||
)
|
||||
|
|
|
@ -30,6 +30,7 @@ mod data;
|
|||
mod docs;
|
||||
mod errors;
|
||||
mod middleware;
|
||||
#[macro_use]
|
||||
mod pages;
|
||||
#[macro_use]
|
||||
mod routes;
|
||||
|
|
|
@ -15,21 +15,36 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use crate::PAGES;
|
||||
use actix_web::{HttpResponse, Responder};
|
||||
use actix_identity::Identity;
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use lazy_static::lazy_static;
|
||||
use my_codegen::get;
|
||||
use sailfish::TemplateOnce;
|
||||
|
||||
use crate::api::v1::auth::runners;
|
||||
use crate::pages::errors::Errorable;
|
||||
use crate::AppData;
|
||||
use crate::PAGES;
|
||||
|
||||
#[derive(Clone, TemplateOnce)]
|
||||
#[template(path = "auth/login/index.html")]
|
||||
struct IndexPage;
|
||||
struct IndexPage {
|
||||
username: Option<String>,
|
||||
password: Option<String>,
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
crate::ImplErrorable!(IndexPage);
|
||||
|
||||
const PAGE: &str = "Login";
|
||||
|
||||
impl Default for IndexPage {
|
||||
fn default() -> Self {
|
||||
IndexPage
|
||||
IndexPage {
|
||||
username: None,
|
||||
password: None,
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,3 +58,28 @@ pub async fn login() -> impl Responder {
|
|||
.content_type("text/html; charset=utf-8")
|
||||
.body(&*INDEX)
|
||||
}
|
||||
#[my_codegen::post(path = "PAGES.auth.login")]
|
||||
async fn login_post(
|
||||
id: Identity,
|
||||
payload: web::Json<runners::Login>,
|
||||
data: AppData,
|
||||
) -> impl Responder {
|
||||
match runners::login_runner(&payload, &data).await {
|
||||
Ok(_) => {
|
||||
id.remember(payload.into_inner().username);
|
||||
HttpResponse::Ok().into()
|
||||
}
|
||||
Err(e) => {
|
||||
let payload = payload.into_inner();
|
||||
let username = Some(payload.username);
|
||||
let password = Some(payload.password);
|
||||
|
||||
let page = IndexPage {
|
||||
username,
|
||||
password,
|
||||
..Default::default()
|
||||
};
|
||||
page.get_error_resp(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ pub mod register;
|
|||
|
||||
pub fn services(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(login::login);
|
||||
cfg.service(login::login_post);
|
||||
cfg.service(register::join);
|
||||
}
|
||||
|
||||
|
|
|
@ -13,12 +13,37 @@
|
|||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use actix_web::{error::ResponseError, web, HttpResponse, Responder};
|
||||
use lazy_static::lazy_static;
|
||||
use sailfish::TemplateOnce;
|
||||
|
||||
use crate::errors::PageError;
|
||||
|
||||
pub trait Errorable: TemplateOnce {
|
||||
fn get_error_resp<E: ResponseError>(self, e: E) -> HttpResponse;
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ImplErrorable {
|
||||
($struct:ident) => {
|
||||
impl crate::pages::errors::Errorable for $struct {
|
||||
fn get_error_resp<E>(mut self, e: E) -> actix_web::HttpResponse
|
||||
where
|
||||
E: actix_web::error::ResponseError + std::fmt::Display,
|
||||
//R: actix_web::Responder
|
||||
{
|
||||
self.error = Some(e.to_string());
|
||||
let page = self.render_once().unwrap();
|
||||
println!("status code: {}", e.status_code());
|
||||
actix_web::dev::HttpResponseBuilder::new(e.status_code())
|
||||
.content_type("text/html; charset=utf-8")
|
||||
.body(&page)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, TemplateOnce)]
|
||||
#[template(path = "errors/index.html")]
|
||||
struct ErrorPage<'a> {
|
||||
|
|
|
@ -10,7 +10,7 @@ use libmcaptcha::defense::Level;
|
|||
use serde::Serialize;
|
||||
|
||||
use super::*;
|
||||
use crate::api::v1::auth::{Login, Register};
|
||||
use crate::api::v1::auth::runners::{Login, Register};
|
||||
use crate::api::v1::mcaptcha::captcha::MCaptchaDetails;
|
||||
use crate::api::v1::mcaptcha::levels::AddLevels;
|
||||
use crate::api::v1::ROUTES;
|
||||
|
|
Loading…
Add table
Reference in a new issue