From 8f87efeeb36dcadba750844d95aae4159b78f9c6 Mon Sep 17 00:00:00 2001 From: realaravinth Date: Sat, 17 Jul 2021 17:43:53 +0530 Subject: [PATCH] error correction, tests for err branches, rm get_token, get_token, delete captcha --- src/api/v1/account/delete.rs | 2 +- src/api/v1/account/test.rs | 30 ++++- src/api/v1/mcaptcha/captcha.rs | 138 ++++++++-------------- src/api/v1/mcaptcha/levels.rs | 40 ++++++- src/api/v1/tests/auth.rs | 11 ++ src/pages/mod.rs | 1 + src/pages/panel/sitekey/delete.rs | 45 +++++++ src/pages/panel/sitekey/login.rs | 45 +++++++ src/pages/panel/sitekey/mod.rs | 4 + src/tests/mod.rs | 1 + templates/api/v1/routes.ts | 14 --- templates/auth/login/index.html | 2 +- templates/auth/register/index.html | 4 +- templates/panel/sitekey/delete/index.html | 29 +++++ 14 files changed, 252 insertions(+), 114 deletions(-) create mode 100644 src/pages/panel/sitekey/delete.rs create mode 100644 src/pages/panel/sitekey/login.rs create mode 100644 templates/panel/sitekey/delete/index.html diff --git a/src/api/v1/account/delete.rs b/src/api/v1/account/delete.rs index b799bbad..edc2d1cd 100644 --- a/src/api/v1/account/delete.rs +++ b/src/api/v1/account/delete.rs @@ -56,7 +56,7 @@ async fn delete_account( Err(ServiceError::WrongPassword) } } - Err(RowNotFound) => Err(ServiceError::UsernameNotFound), + Err(RowNotFound) => Err(ServiceError::AccountNotFound), Err(_) => Err(ServiceError::InternalServerError), } } diff --git a/src/api/v1/account/test.rs b/src/api/v1/account/test.rs index 0e89b764..10e32fe5 100644 --- a/src/api/v1/account/test.rs +++ b/src/api/v1/account/test.rs @@ -25,6 +25,7 @@ use crate::api::v1::ROUTES; use crate::data::Data; use crate::*; +use crate::errors::*; use crate::tests::*; #[actix_rt::test] @@ -144,17 +145,38 @@ async fn email_udpate_password_validation_del_userworks() { assert_eq!(email_update_resp.status(), StatusCode::OK); - let payload = Password { - password: creds.password, + let mut payload = Password { + password: NAME.into(), }; + bad_post_req_test( + NAME, + PASSWORD, + ROUTES.account.delete, + &payload, + ServiceError::WrongPassword, + StatusCode::UNAUTHORIZED, + ) + .await; + payload.password = PASSWORD.into(); let delete_user_resp = test::call_service( + &app, + post_request!(&payload, ROUTES.account.delete) + .cookie(cookies.clone()) + .to_request(), + ) + .await; + + assert_eq!(delete_user_resp.status(), StatusCode::OK); + + let account_not_found_resp = test::call_service( &app, post_request!(&payload, ROUTES.account.delete) .cookie(cookies) .to_request(), ) .await; - - assert_eq!(delete_user_resp.status(), StatusCode::OK); + assert_eq!(account_not_found_resp.status(), StatusCode::NOT_FOUND); + let txt: ErrorToResponse = test::read_body_json(account_not_found_resp).await; + assert_eq!(txt.error, format!("{}", ServiceError::AccountNotFound)); } diff --git a/src/api/v1/mcaptcha/captcha.rs b/src/api/v1/mcaptcha/captcha.rs index d56bf83c..b99e2f3d 100644 --- a/src/api/v1/mcaptcha/captcha.rs +++ b/src/api/v1/mcaptcha/captcha.rs @@ -27,7 +27,6 @@ use crate::AppData; pub mod routes { pub struct MCaptcha { pub delete: &'static str, - pub get_token: &'static str, pub update_key: &'static str, } @@ -35,7 +34,6 @@ pub mod routes { pub const fn new() -> MCaptcha { MCaptcha { update_key: "/api/v1/mcaptcha/update/key", - get_token: "/api/v1/mcaptcha/get", delete: "/api/v1/mcaptcha/delete", } } @@ -45,7 +43,6 @@ pub mod routes { pub fn services(cfg: &mut web::ServiceConfig) { cfg.service(update_token); cfg.service(delete_mcaptcha); - cfg.service(get_token); } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -163,35 +160,10 @@ async fn update_token_helper( Ok(()) } -#[my_codegen::post( - path = "crate::V1_API_ROUTES.mcaptcha.get_token", - wrap = "crate::CheckLogin" -)] -async fn get_token( - payload: web::Json, - data: AppData, - id: Identity, -) -> ServiceResult { - let username = id.identity().unwrap(); - let res = match sqlx::query_as!( - MCaptchaDetails, - "SELECT key, name from mcaptcha_config - WHERE key = ($1) AND user_id = (SELECT ID FROM mcaptcha_users WHERE name = $2) ", - &payload.key, - &username, - ) - .fetch_one(&data.db) - .await - { - Err(sqlx::Error::RowNotFound) => Err(ServiceError::TokenNotFound), - Ok(m) => Ok(m), - Err(e) => { - let e: ServiceError = e.into(); - Err(e) - } - }?; - - Ok(HttpResponse::Ok().json(res)) +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct DeleteCaptcha { + pub key: String, + pub password: String, } #[my_codegen::post( @@ -199,21 +171,58 @@ async fn get_token( wrap = "crate::CheckLogin" )] async fn delete_mcaptcha( - payload: web::Json, + payload: web::Json, data: AppData, id: Identity, ) -> ServiceResult { + use argon2_creds::Config; + use sqlx::Error::RowNotFound; + let username = id.identity().unwrap(); - sqlx::query!( - "DELETE FROM mcaptcha_config - WHERE key = ($1) AND user_id = (SELECT ID FROM mcaptcha_users WHERE name = $2) ", - &payload.key, + struct PasswordID { + password: String, + id: i32, + } + + let rec = sqlx::query_as!( + PasswordID, + r#"SELECT ID, password FROM mcaptcha_users WHERE name = ($1)"#, &username, ) - .execute(&data.db) - .await?; - Ok(HttpResponse::Ok()) + .fetch_one(&data.db) + .await; + + match rec { + Ok(rec) => { + if Config::verify(&rec.password, &payload.password)? { + sqlx::query!( + "DELETE FROM mcaptcha_levels + WHERE config_id = ( + SELECT config_id FROM mcaptcha_config + WHERE key = $1 AND user_id = $2 + );", + &payload.key, + &rec.id, + ) + .execute(&data.db) + .await?; + + sqlx::query!( + "DELETE FROM mcaptcha_config WHERE key = ($1) AND user_id = $2;", + &payload.key, + &rec.id, + ) + .execute(&data.db) + .await?; + Ok(HttpResponse::Ok()) + } else { + Err(ServiceError::WrongPassword) + } + } + Err(RowNotFound) => Err(ServiceError::UsernameNotFound), + Err(_) => Err(ServiceError::InternalServerError), + } } // Workflow: @@ -235,34 +244,6 @@ mod tests { use crate::tests::*; use crate::*; - #[actix_rt::test] - async fn add_mcaptcha_works() { - const NAME: &str = "testusermcaptcha"; - const PASSWORD: &str = "longpassworddomain"; - const EMAIL: &str = "testusermcaptcha@a.com"; - - { - let data = Data::new().await; - delete_user(NAME, &data).await; - } - - // 1. add mcaptcha token - register_and_signin(NAME, EMAIL, PASSWORD).await; - let (data, _, signin_resp, token_key) = add_levels_util(NAME, PASSWORD).await; - let cookies = get_cookie!(signin_resp); - let app = get_app!(data).await; - - // 4. delete token - let del_token = test::call_service( - &app, - post_request!(&token_key, ROUTES.mcaptcha.delete) - .cookie(cookies.clone()) - .to_request(), - ) - .await; - assert_eq!(del_token.status(), StatusCode::OK); - } - #[actix_rt::test] async fn update_and_get_mcaptcha_works() { const NAME: &str = "updateusermcaptcha"; @@ -292,30 +273,15 @@ mod tests { let updated_token: MCaptchaDetails = test::read_body_json(update_token_resp).await; - // get token key with updated key + // get levels with udpated key let get_token_resp = test::call_service( &app, - post_request!(&updated_token, ROUTES.mcaptcha.get_token) + post_request!(&updated_token, ROUTES.levels.get) .cookie(cookies.clone()) .to_request(), ) .await; + // if updated key doesn't exist in databse, a non 200 result will bereturned assert_eq!(get_token_resp.status(), StatusCode::OK); - - // check if they match - let mut get_token_key: MCaptchaDetails = - test::read_body_json(get_token_resp).await; - assert_eq!(get_token_key.key, updated_token.key); - - get_token_key.key = "nonexistent".into(); - - let get_nonexistent_token_resp = test::call_service( - &app, - post_request!(&get_token_key, ROUTES.mcaptcha.get_token) - .cookie(cookies.clone()) - .to_request(), - ) - .await; - assert_eq!(get_nonexistent_token_resp.status(), StatusCode::NOT_FOUND); } } diff --git a/src/api/v1/mcaptcha/levels.rs b/src/api/v1/mcaptcha/levels.rs index 5955a9d1..466029a4 100644 --- a/src/api/v1/mcaptcha/levels.rs +++ b/src/api/v1/mcaptcha/levels.rs @@ -30,20 +30,20 @@ pub mod routes { pub struct Levels { pub add: &'static str, - // pub delete: &'static str, + pub delete: &'static str, pub get: &'static str, pub update: &'static str, } impl Levels { pub const fn new() -> Levels { - let add = "/api/v1/mcaptcha/levels/add"; - let update = "/api/v1/mcaptcha/levels/update"; - // let delete = "/api/v1/mcaptcha/levels/delete"; - let get = "/api/v1/mcaptcha/levels/get"; + let add = "/api/v1/mcaptcha/add"; + let update = "/api/v1/mcaptcha/update"; + let delete = "/api/v1/mcaptcha/delete"; + let get = "/api/v1/mcaptcha/get"; Levels { add, - // delete, + delete, get, update, } @@ -252,6 +252,7 @@ mod tests { use actix_web::test; use super::*; + use crate::api::v1::mcaptcha::captcha::DeleteCaptcha; use crate::api::v1::ROUTES; use crate::data::Data; use crate::tests::*; @@ -326,5 +327,32 @@ mod tests { assert_eq!(get_level_resp.status(), StatusCode::OK); let res_levels: Vec = test::read_body_json(get_level_resp).await; assert_eq!(res_levels, levels); + + // 4. delete captcha + let mut delete_payload = DeleteCaptcha { + key: key.key, + password: format!("worongpass{}", PASSWORD), + }; + + bad_post_req_test( + NAME, + PASSWORD, + ROUTES.mcaptcha.delete, + &delete_payload, + ServiceError::WrongPassword, + StatusCode::UNAUTHORIZED, + ) + .await; + + delete_payload.password = PASSWORD.into(); + + let del_resp = test::call_service( + &app, + post_request!(&delete_payload, ROUTES.mcaptcha.delete) + .cookie(cookies.clone()) + .to_request(), + ) + .await; + assert_eq!(del_resp.status(), StatusCode::OK); } } diff --git a/src/api/v1/tests/auth.rs b/src/api/v1/tests/auth.rs index edd66ee1..bf274cd6 100644 --- a/src/api/v1/tests/auth.rs +++ b/src/api/v1/tests/auth.rs @@ -102,6 +102,17 @@ async fn auth_works() { ) .await; + creds.login = "nonexistantuser@example.com".into(); + bad_post_req_test( + NAME, + PASSWORD, + ROUTES.auth.login, + &creds, + ServiceError::AccountNotFound, + StatusCode::NOT_FOUND, + ) + .await; + // 4. trying to signin with wrong password creds.login = NAME.into(); creds.password = NAME.into(); diff --git a/src/pages/mod.rs b/src/pages/mod.rs index 11dedc09..46f9a6de 100644 --- a/src/pages/mod.rs +++ b/src/pages/mod.rs @@ -61,6 +61,7 @@ mod tests { PAGES.panel.sitekey.add, PAGES.panel.sitekey.list, PAGES.panel.notifications, + "/sitekey/test/delete", ]; for url in urls.iter() { diff --git a/src/pages/panel/sitekey/delete.rs b/src/pages/panel/sitekey/delete.rs new file mode 100644 index 00000000..0159b212 --- /dev/null +++ b/src/pages/panel/sitekey/delete.rs @@ -0,0 +1,45 @@ +/* +* Copyright (C) 2021 Aravinth Manivannan +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as +* published by the Free Software Foundation, either version 3 of the +* License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see . +*/ + +use actix_web::{HttpResponse, Responder}; +use lazy_static::lazy_static; +use my_codegen::get; +use sailfish::TemplateOnce; + +use crate::PAGES; + +#[derive(Clone, TemplateOnce)] +#[template(path = "panel/sitekey/delete/index.html")] +struct IndexPage; +const PAGE: &str = "Confirm Access"; + +impl Default for IndexPage { + fn default() -> Self { + IndexPage + } +} + +lazy_static! { + static ref INDEX: String = IndexPage::default().render_once().unwrap(); +} + +#[get(path = "PAGES.panel.sitekey.delete", wrap = "crate::CheckLogin")] +pub async fn delete_sitekey() -> impl Responder { + HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body(&*INDEX) +} diff --git a/src/pages/panel/sitekey/login.rs b/src/pages/panel/sitekey/login.rs new file mode 100644 index 00000000..803ea103 --- /dev/null +++ b/src/pages/panel/sitekey/login.rs @@ -0,0 +1,45 @@ +/* +* Copyright (C) 2021 Aravinth Manivannan +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as +* published by the Free Software Foundation, either version 3 of the +* License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see . +*/ + +use actix_web::{HttpResponse, Responder}; +use lazy_static::lazy_static; +use my_codegen::get; +use sailfish::TemplateOnce; + +use crate::PAGES; + +#[derive(Clone, TemplateOnce)] +#[template(path = "auth/login/index.html")] +struct IndexPage; +const PAGE: &str = "Login"; + +impl Default for IndexPage { + fn default() -> Self { + IndexPage + } +} + +lazy_static! { + static ref INDEX: String = IndexPage::default().render_once().unwrap(); +} + +#[get(path = "PAGES.auth.login")] +pub async fn login() -> impl Responder { + HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body(&*INDEX) +} diff --git a/src/pages/panel/sitekey/mod.rs b/src/pages/panel/sitekey/mod.rs index 68c1f606..f8714c5d 100644 --- a/src/pages/panel/sitekey/mod.rs +++ b/src/pages/panel/sitekey/mod.rs @@ -16,6 +16,7 @@ */ mod add; +mod delete; mod edit; pub mod list; mod view; @@ -26,6 +27,7 @@ pub mod routes { pub add: &'static str, pub view: &'static str, pub edit: &'static str, + pub delete: &'static str, } impl Sitekey { @@ -35,6 +37,7 @@ pub mod routes { add: "/sitekeys/add", view: "/sitekey/{key}", edit: "/sitekey/{key}/edit", + delete: "/sitekey/{key}/delete", } } } @@ -45,4 +48,5 @@ pub fn services(cfg: &mut actix_web::web::ServiceConfig) { cfg.service(list::list_sitekeys); cfg.service(view::view_sitekey); cfg.service(edit::edit_sitekey); + cfg.service(delete::delete_sitekey); } diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 926ff4b5..245ff1a5 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -159,6 +159,7 @@ pub async fn bad_post_req_test( .await; assert_eq!(dup_token_resp.status(), s); let txt: ErrorToResponse = test::read_body_json(dup_token_resp).await; + //println!("{}", txt.error); assert_eq!(txt.error, format!("{}", dup_err)); } diff --git a/templates/api/v1/routes.ts b/templates/api/v1/routes.ts index ff914199..95b17644 100644 --- a/templates/api/v1/routes.ts +++ b/templates/api/v1/routes.ts @@ -24,20 +24,6 @@ const ROUTES = { emailExists: '/api/v1/account/email/exists', healthCheck: '/api/v1/meta/health', buildDetails: '/api/v1/meta/build', - addDomain: '/api/v1/mcaptcha/domain/add', - challengeDomain: '/api/v1/mcaptcha/domain/domain/verify/challenge/get', - proveDomain: '/api/v1/mcaptcha/domain/domain/verify/challenge/prove', - deleteDomain: '/api/v1/mcaptcha/domain/delete', - addToken: '/api/v1/mcaptcha/domain/token/add', - updateTokenKey: '/api/v1/mcaptcha/domain/token/update', - getTokenKey: '/api/v1/mcaptcha/domain/token/get', - deleteToken: '/api/v1/mcaptcha/domain/token/delete', - addTokenLevels: '/api/v1/mcaptcha/domain/token/levels/add', - updateTokenLevels: '/api/v1/mcaptcha/domain/token/levels/update', - deleteTokenLevels: '/api/v1/mcaptcha/domain/token/levels/delete', - getTokenLevels: '/api/v1/mcaptcha/domain/token/levels/get', - getTokenDuration: '/api/v1/mcaptcha/domain/token/token/get', - updateTokenDuration: '/api/v1/mcaptcha/domain/token/token/update', markNotificationRead: '/api/v1/notifications/read', }; diff --git a/templates/auth/login/index.html b/templates/auth/login/index.html index 1d780185..01fcbd11 100644 --- a/templates/auth/login/index.html +++ b/templates/auth/login/index.html @@ -25,7 +25,7 @@ /> -