diff --git a/src/api/v1/account/delete.rs b/src/api/v1/account/delete.rs
index 9539df06..d897d7c4 100644
--- a/src/api/v1/account/delete.rs
+++ b/src/api/v1/account/delete.rs
@@ -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;
diff --git a/src/api/v1/account/test.rs b/src/api/v1/account/test.rs
index a949f9f4..adffa590 100644
--- a/src/api/v1/account/test.rs
+++ b/src/api/v1/account/test.rs
@@ -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::*;
diff --git a/src/api/v1/auth.rs b/src/api/v1/auth.rs
index 69f8c022..b5869190 100644
--- a/src/api/v1/auth.rs
+++ b/src/api/v1/auth.rs
@@ -14,12 +14,10 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-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,
+ }
+
+ #[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,
-}
-
-#[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,
+async fn register(
+ payload: web::Json,
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());
- }
- };
- }
+ 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,
+ payload: web::Json,
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)? {
- 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")]
diff --git a/src/api/v1/tests/auth.rs b/src/api/v1/tests/auth.rs
index 9ec79a1a..03014184 100644
--- a/src/api/v1/tests/auth.rs
+++ b/src/api/v1/tests/auth.rs
@@ -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,
)
diff --git a/src/main.rs b/src/main.rs
index 72be2977..af9da47a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -30,6 +30,7 @@ mod data;
mod docs;
mod errors;
mod middleware;
+#[macro_use]
mod pages;
#[macro_use]
mod routes;
diff --git a/src/pages/auth/login.rs b/src/pages/auth/login.rs
index 4c49c8f5..02da32a9 100644
--- a/src/pages/auth/login.rs
+++ b/src/pages/auth/login.rs
@@ -15,21 +15,36 @@
* along with this program. If not, see .
*/
-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,
+ password: Option,
+ error: Option,
+}
+
+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,
+ 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)
+ }
+ }
+}
diff --git a/src/pages/auth/mod.rs b/src/pages/auth/mod.rs
index fa7bb602..a758ac0b 100644
--- a/src/pages/auth/mod.rs
+++ b/src/pages/auth/mod.rs
@@ -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);
}
diff --git a/src/pages/errors.rs b/src/pages/errors.rs
index 9495c473..df72995a 100644
--- a/src/pages/errors.rs
+++ b/src/pages/errors.rs
@@ -13,12 +13,37 @@
*
* You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */
-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(self, e: E) -> HttpResponse;
+}
+
+#[macro_export]
+macro_rules! ImplErrorable {
+ ($struct:ident) => {
+ impl crate::pages::errors::Errorable for $struct {
+ fn get_error_resp(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> {
diff --git a/src/tests/mod.rs b/src/tests/mod.rs
index 4049d2fb..81d90437 100644
--- a/src/tests/mod.rs
+++ b/src/tests/mod.rs
@@ -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;