errorable and seperated runner methods for auth

This commit is contained in:
realaravinth
2021-06-28 19:16:59 +05:30
parent 2162d32455
commit cc17f2048f
9 changed files with 222 additions and 135 deletions

View File

@@ -18,7 +18,7 @@
use actix_identity::Identity; use actix_identity::Identity;
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use super::auth::Password; use super::auth::runners::Password;
use crate::errors::*; use crate::errors::*;
use crate::AppData; use crate::AppData;

View File

@@ -20,7 +20,7 @@ use actix_web::test;
use super::email::*; use super::email::*;
use super::*; use super::*;
use crate::api::v1::auth::*; use crate::api::v1::auth::runners::Password;
use crate::api::v1::ROUTES; use crate::api::v1::ROUTES;
use crate::data::Data; use crate::data::Data;
use crate::*; use crate::*;

View File

@@ -14,12 +14,10 @@
* You should have received a copy of the GNU Affero General Public License * 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/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::borrow::Cow;
use actix_identity::Identity; use actix_identity::Identity;
use actix_web::http::header; use actix_web::http::header;
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use log::debug;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::mcaptcha::get_random; use super::mcaptcha::get_random;
@@ -47,36 +45,60 @@ pub mod routes {
} }
} }
pub fn services(cfg: &mut web::ServiceConfig) { pub mod runners {
cfg.service(signup); use std::borrow::Cow;
cfg.service(signin);
cfg.service(signout);
}
#[derive(Clone, Debug, Deserialize, Serialize)] use super::*;
pub struct Register {
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Register {
pub username: String, pub username: String,
pub password: String, pub password: String,
pub confirm_password: String, pub confirm_password: String,
pub email: Option<String>, pub email: Option<String>,
} }
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Login { pub struct Login {
pub username: String, pub username: String,
pub password: String, pub password: String,
} }
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Password { pub struct Password {
pub password: String, pub password: String,
} }
#[my_codegen::post(path = "crate::V1_API_ROUTES.auth.register")] /// returns Ok(()) when everything checks out and the user is authenticated. Erros otherwise
async fn signup( pub async fn login_runner(payload: &Login, data: &AppData) -> ServiceResult<()> {
payload: web::Json<Register>, use argon2_creds::Config;
data: AppData, use sqlx::Error::RowNotFound;
) -> ServiceResult<impl Responder> {
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 { if !crate::SETTINGS.server.allow_registration {
return Err(ServiceError::ClosedForRegistration); return Err(ServiceError::ClosedForRegistration);
} }
@@ -135,39 +157,33 @@ async fn signup(
} }
}; };
} }
Ok(())
}
}
pub fn services(cfg: &mut web::ServiceConfig) {
cfg.service(register);
cfg.service(login);
cfg.service(signout);
}
#[my_codegen::post(path = "crate::V1_API_ROUTES.auth.register")]
async fn register(
payload: web::Json<runners::Register>,
data: AppData,
) -> ServiceResult<impl Responder> {
runners::register_runner(&payload, &data).await?;
Ok(HttpResponse::Ok()) Ok(HttpResponse::Ok())
} }
#[my_codegen::post(path = "crate::V1_API_ROUTES.auth.login")] #[my_codegen::post(path = "crate::V1_API_ROUTES.auth.login")]
async fn signin( async fn login(
id: Identity, id: Identity,
payload: web::Json<Login>, payload: web::Json<runners::Login>,
data: AppData, data: AppData,
) -> ServiceResult<impl Responder> { ) -> ServiceResult<impl Responder> {
use argon2_creds::Config; runners::login_runner(&payload, &data).await?;
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); id.remember(payload.into_inner().username);
Ok(HttpResponse::Ok()) Ok(HttpResponse::Ok())
} else {
Err(ServiceError::WrongPassword)
}
}
Err(RowNotFound) => Err(ServiceError::UsernameNotFound),
Err(_) => Err(ServiceError::InternalServerError),
}
} }
#[my_codegen::get(path = "crate::V1_API_ROUTES.auth.logout", wrap = "crate::CheckLogin")] #[my_codegen::get(path = "crate::V1_API_ROUTES.auth.logout", wrap = "crate::CheckLogin")]

View File

@@ -18,7 +18,7 @@
use actix_web::http::{header, StatusCode}; use actix_web::http::{header, StatusCode};
use actix_web::test; use actix_web::test;
use crate::api::v1::auth::*; use crate::api::v1::auth::runners::{Login, Register};
use crate::api::v1::ROUTES; use crate::api::v1::ROUTES;
use crate::data::Data; use crate::data::Data;
use crate::errors::*; use crate::errors::*;
@@ -57,17 +57,6 @@ async fn auth_works() {
let (_, _, signin_resp) = register_and_signin(NAME, EMAIL, PASSWORD).await; let (_, _, signin_resp) = register_and_signin(NAME, EMAIL, PASSWORD).await;
let cookies = get_cookie!(signin_resp); 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 // 2. check if duplicate username is allowed
let msg = Register { let msg = Register {
username: NAME.into(), username: NAME.into(),
@@ -86,7 +75,7 @@ async fn auth_works() {
.await; .await;
// 3. sigining in with non-existent user // 3. sigining in with non-existent user
let mut login = Login { let mut creds = Login {
username: "nonexistantuser".into(), username: "nonexistantuser".into(),
password: msg.password.clone(), password: msg.password.clone(),
}; };
@@ -94,21 +83,36 @@ async fn auth_works() {
NAME, NAME,
PASSWORD, PASSWORD,
ROUTES.auth.login, ROUTES.auth.login,
&login, &creds,
ServiceError::UsernameNotFound, ServiceError::UsernameNotFound,
StatusCode::NOT_FOUND, StatusCode::NOT_FOUND,
) )
.await; .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 // 4. trying to signin with wrong password
login.username = NAME.into(); creds.username = NAME.into();
login.password = NAME.into(); creds.password = NAME.into();
bad_post_req_test( bad_post_req_test(
NAME, NAME,
PASSWORD, PASSWORD,
ROUTES.auth.login, ROUTES.auth.login,
&login, &creds,
ServiceError::WrongPassword, ServiceError::WrongPassword,
StatusCode::UNAUTHORIZED, StatusCode::UNAUTHORIZED,
) )

View File

@@ -30,6 +30,7 @@ mod data;
mod docs; mod docs;
mod errors; mod errors;
mod middleware; mod middleware;
#[macro_use]
mod pages; mod pages;
#[macro_use] #[macro_use]
mod routes; mod routes;

View File

@@ -15,21 +15,36 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use crate::PAGES; use actix_identity::Identity;
use actix_web::{HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use my_codegen::get; use my_codegen::get;
use sailfish::TemplateOnce; use sailfish::TemplateOnce;
use crate::api::v1::auth::runners;
use crate::pages::errors::Errorable;
use crate::AppData;
use crate::PAGES;
#[derive(Clone, TemplateOnce)] #[derive(Clone, TemplateOnce)]
#[template(path = "auth/login/index.html")] #[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"; const PAGE: &str = "Login";
impl Default for IndexPage { impl Default for IndexPage {
fn default() -> Self { 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") .content_type("text/html; charset=utf-8")
.body(&*INDEX) .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)
}
}
}

View File

@@ -20,6 +20,7 @@ pub mod register;
pub fn services(cfg: &mut actix_web::web::ServiceConfig) { pub fn services(cfg: &mut actix_web::web::ServiceConfig) {
cfg.service(login::login); cfg.service(login::login);
cfg.service(login::login_post);
cfg.service(register::join); cfg.service(register::join);
} }

View File

@@ -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/>. */ * 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 lazy_static::lazy_static;
use sailfish::TemplateOnce; use sailfish::TemplateOnce;
use crate::errors::PageError; 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)] #[derive(Clone, TemplateOnce)]
#[template(path = "errors/index.html")] #[template(path = "errors/index.html")]
struct ErrorPage<'a> { struct ErrorPage<'a> {

View File

@@ -10,7 +10,7 @@ use libmcaptcha::defense::Level;
use serde::Serialize; use serde::Serialize;
use super::*; 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::captcha::MCaptchaDetails;
use crate::api::v1::mcaptcha::levels::AddLevels; use crate::api::v1::mcaptcha::levels::AddLevels;
use crate::api::v1::ROUTES; use crate::api::v1::ROUTES;