diff --git a/migrations/20210310122339_mcaptcha_domains_verified.sql b/migrations/20210310122339_mcaptcha_domains_verified.sql deleted file mode 100644 index 12e9506e..00000000 --- a/migrations/20210310122339_mcaptcha_domains_verified.sql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE TABLE IF NOT EXISTS mcaptcha_domains_verified ( - name VARCHAR(100) PRIMARY KEY NOT NULL UNIQUE, - owner_id INTEGER references mcaptcha_users(ID) ON DELETE CASCADE NOT NULL -); diff --git a/migrations/20210310122617_mcaptcha_config.sql b/migrations/20210310122617_mcaptcha_config.sql index 6dd8521e..9ae6eb34 100644 --- a/migrations/20210310122617_mcaptcha_config.sql +++ b/migrations/20210310122617_mcaptcha_config.sql @@ -1,7 +1,7 @@ CREATE TABLE IF NOT EXISTS mcaptcha_config ( config_id SERIAL PRIMARY KEY NOT NULL, - domain_name varchar(100) NOT NULL references mcaptcha_domains_verified(name) ON DELETE CASCADE, + user_id INTEGER NOT NULL references mcaptcha_users(ID) ON DELETE CASCADE, key varchar(100) NOT NULL UNIQUE, - name varchar(100) NOT NULL UNIQUE, + name varchar(100) DEFAULT NULL, duration integer NOT NULL DEFAULT 30 ); diff --git a/migrations/20210324130238_mcaptcha_domains_unverified.sql b/migrations/20210324130238_mcaptcha_domains_unverified.sql deleted file mode 100644 index d6037cb3..00000000 --- a/migrations/20210324130238_mcaptcha_domains_unverified.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE IF NOT EXISTS mcaptcha_domains_unverified ( - name VARCHAR(100) PRIMARY KEY NOT NULL, - owner_id INTEGER references mcaptcha_users(ID) ON DELETE CASCADE NOT NULL, - verified BOOLEAN DEFAULT NULL, - verification_challenge VARCHAR(32) NOT NULL -); diff --git a/src/api/v1/mcaptcha/domains.rs b/src/api/v1/mcaptcha/domains.rs deleted file mode 100644 index f2b965b0..00000000 --- a/src/api/v1/mcaptcha/domains.rs +++ /dev/null @@ -1,324 +0,0 @@ -/* -* 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_identity::Identity; -use actix_web::{client::Client, post, web, HttpResponse, Responder}; -//use awc::Client; -use serde::{Deserialize, Serialize}; -use url::Url; - -use super::{get_random, is_authenticated}; -use crate::errors::*; -use crate::Data; - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Domain { - pub name: String, -} - -#[post("/api/v1/mcaptcha/domain/add")] -pub async fn add_domain( - payload: web::Json, - data: web::Data, - id: Identity, -) -> ServiceResult { - is_authenticated(&id)?; - let url = Url::parse(&payload.name)?; - - let host = url.host_str().ok_or(ServiceError::NotAUrl)?; - let user = id.identity().unwrap(); - let challenge = get_random(32); - let res = sqlx::query!( - "INSERT INTO mcaptcha_domains_unverified - (name, owner_id, verification_challenge) VALUES - ($1, (SELECT ID FROM mcaptcha_users WHERE name = ($2) ), $3);", - host, - user, - challenge - ) - .execute(&data.db) - .await; - match res { - Err(e) => Err(dup_error(e, ServiceError::HostnameTaken)), - Ok(_) => Ok(HttpResponse::Ok()), - } -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Challenge { - verification_challenge: String, -} - -#[post("/api/v1/mcaptcha/domain/verify/challenge/get")] -pub async fn get_challenge( - payload: web::Json, - data: web::Data, - id: Identity, -) -> ServiceResult { - is_authenticated(&id)?; - let url = Url::parse(&payload.name)?; - - let host = url.host_str().ok_or(ServiceError::NotAUrl)?; - let user = id.identity().unwrap(); - let res = sqlx::query_as!( - Challenge, - "SELECT verification_challenge - FROM mcaptcha_domains_unverified where - name = $1 AND owner_id = (SELECT ID from mcaptcha_users where name = $2)", - host, - user, - ) - .fetch_one(&data.db) - .await?; - Ok(HttpResponse::Ok().json(res)) -} - -#[post("/api/v1/mcaptcha/domain/verify/challenge/prove")] -pub async fn verify( - payload: web::Json, - data: web::Data, - client: web::Data, - id: Identity, -) -> ServiceResult { - use crate::VERIFICATION_PATH; - use futures::{future::TryFutureExt, try_join}; - - is_authenticated(&id)?; - let url = Url::parse(&payload.name)?; - let host = url.host_str().ok_or(ServiceError::NotAUrl)?; - let user = id.identity().unwrap(); - let challenge_fut = sqlx::query_as!( - Challenge, - "SELECT verification_challenge - FROM mcaptcha_domains_unverified where - name = $1 AND owner_id = (SELECT ID from mcaptcha_users where name = $2)", - &host, - &user, - ) - .fetch_one(&data.db) - .map_err(|e| { - let r: ServiceError = e.into(); - r - }); - - let res_fut = client - .get(format!("{}{}", url.to_string(), VERIFICATION_PATH)) - .send() - .map_err(|e| { - let r: ServiceError = e.into(); - r - }); - - let (challenge, mut server_res) = try_join!(challenge_fut, res_fut)?; - - let server_resp: Challenge = server_res - .json() - .await - .map_err(|_| return ServiceError::ChallengeCourruption)?; - - if server_resp.verification_challenge == challenge.verification_challenge { - sqlx::query!( - "INSERT INTO mcaptcha_domains_verified (name, owner_id) VALUES - ($1, (SELECT ID from mcaptcha_users WHERE name = $2))", - &host, - &user - ) - .execute(&data.db) - .await?; - - // TODO delete staging unverified - - Ok(HttpResponse::Ok()) - } else { - Err(ServiceError::ChallengeVerificationFailure) - } -} - -#[post("/api/v1/mcaptcha/domain/delete")] -pub async fn delete_domain( - payload: web::Json, - data: web::Data, - id: Identity, -) -> ServiceResult { - is_authenticated(&id)?; - let url = Url::parse(&payload.name)?; - let host = url.host_str().ok_or(ServiceError::NotAUrl)?; - sqlx::query!( - "DELETE FROM mcaptcha_domains_verified WHERE name = ($1)", - host, - ) - .execute(&data.db) - .await?; - // TODO check running actors and delete - // if domain(api_key) matches mcaptcha actor id - Ok(HttpResponse::Ok()) -} - -// Workflow: -// 1. Sign up -// 2. Sign in -// 3. Add domain(DNS TXT record verification? / put string at path) -// 4. Create token -// 5. Add levels -// 6. Update duration -// 7. Start syatem - -#[cfg(test)] -mod tests { - use actix_web::http::{header, StatusCode}; - use actix_web::test; - - use super::*; - use crate::api::v1::services as v1_services; - use crate::tests::*; - use crate::*; - - #[actix_rt::test] - async fn add_domains_work() { - const NAME: &str = "testuserdomainn"; - const PASSWORD: &str = "longpassworddomain"; - const EMAIL: &str = "testuserdomain@a.com"; - const DOMAIN: &str = "http://example.com"; - const ADD_URL: &str = "/api/v1/mcaptcha/domain/add"; - - { - let data = Data::new().await; - delete_user(NAME, &data).await; - } - - register_and_signin(NAME, EMAIL, PASSWORD).await; - - // 1. add domain - let (data, _, signin_resp) = add_domain_util(NAME, PASSWORD, DOMAIN).await; - let cookies = get_cookie!(signin_resp); - let mut app = get_app!(data).await; - - let mut domain = Domain { - name: DOMAIN.into(), - }; - - // 2. duplicate domain - bad_post_req_test( - NAME, - PASSWORD, - ADD_URL, - &domain, - ServiceError::HostnameTaken, - StatusCode::BAD_REQUEST, - ) - .await; - - // 3. delete domain - let del_domain_resp = test::call_service( - &mut app, - post_request!(&domain, "/api/v1/mcaptcha/domain/delete") - .cookie(cookies.clone()) - .to_request(), - ) - .await; - assert_eq!(del_domain_resp.status(), StatusCode::OK); - - // 4. not a URL test for adding domain - domain.name = "testing".into(); - bad_post_req_test( - NAME, - PASSWORD, - ADD_URL, - &domain, - ServiceError::NotAUrl, - StatusCode::BAD_REQUEST, - ) - .await; - } - - #[actix_rt::test] - async fn domain_verification_works() { - use crate::api::v1::tests::*; - use std::sync::mpsc; - use std::thread; - - const NAME: &str = "testdomainveri"; - const PASSWORD: &str = "longpassworddomain"; - const EMAIL: &str = "domainverification@a.com"; - const DOMAIN: &str = "http://localhost:18001"; - const IP: &str = "localhost:18001"; - const CHALLENGE_GET: &str = "/api/v1/mcaptcha/domain/verify/challenge/get"; - const CHALLENGE_VERIFY: &str = "/api/v1/mcaptcha/domain/verify/challenge/prove"; - { - let data = Data::new().await; - delete_user(NAME, &data).await; - } - - let (tx, rx) = mpsc::channel(); - thread::spawn(move || { - actix_rt::System::new("").block_on(server(IP, tx)); - }); - let srv = rx.recv().unwrap(); - - let client = Client::new(); - - let (data, _, signin_resp) = register_and_signin(NAME, EMAIL, PASSWORD).await; - let cookies = get_cookie!(signin_resp); - let mut app = test::init_service( - App::new() - .wrap(get_identity_service()) - .configure(v1_services) - .data(data.clone()) - .data(client.clone()), - ) - .await; - - let domain = Domain { - name: DOMAIN.into(), - }; - - let add_domain_resp = test::call_service( - &mut app, - post_request!(&domain, "/api/v1/mcaptcha/domain/add") - .cookie(cookies.clone()) - .to_request(), - ) - .await; - assert_eq!(add_domain_resp.status(), StatusCode::OK); - - let get_challenge_resp = test::call_service( - &mut app, - post_request!(&domain, CHALLENGE_GET) - .cookie(cookies.clone()) - .to_request(), - ) - .await; - assert_eq!(get_challenge_resp.status(), StatusCode::OK); - let challenge: Challenge = test::read_body_json(get_challenge_resp).await; - - client - .post(format!("{}/{}/", DOMAIN, VERIFICATION_PATH)) - .send_json(&challenge) - .await - .unwrap(); - - let verify_challenge_resp = test::call_service( - &mut app, - post_request!(&domain, CHALLENGE_VERIFY) - .cookie(cookies.clone()) - .to_request(), - ) - .await; - assert_eq!(verify_challenge_resp.status(), StatusCode::OK); - srv.stop(true).await; - } -} diff --git a/src/api/v1/mcaptcha/duration.rs b/src/api/v1/mcaptcha/duration.rs index a1551ed7..4d518e3c 100644 --- a/src/api/v1/mcaptcha/duration.rs +++ b/src/api/v1/mcaptcha/duration.rs @@ -20,12 +20,13 @@ use actix_web::{post, web, HttpResponse, Responder}; use serde::{Deserialize, Serialize}; use super::is_authenticated; +use crate::api::v1::mcaptcha::mcaptcha::MCaptchaDetails; use crate::errors::*; use crate::Data; #[derive(Deserialize, Serialize)] pub struct UpdateDuration { - pub token_name: String, + pub key: String, pub duration: i32, } @@ -36,13 +37,15 @@ pub async fn update_duration( id: Identity, ) -> ServiceResult { is_authenticated(&id)?; + let username = id.identity().unwrap(); if payload.duration > 0 { sqlx::query!( - "UPDATE mcaptcha_config set duration = $1 WHERE - name = $2;", + "UPDATE mcaptcha_config set duration = $1 + WHERE key = $2 AND user_id = (SELECT ID FROM mcaptcha_users WHERE name = $3)", &payload.duration, - &payload.token_name, + &payload.key, + &username, ) .execute(&data.db) .await?; @@ -68,17 +71,19 @@ pub struct GetDuration { #[post("/api/v1/mcaptcha/domain/token/duration/get")] pub async fn get_duration( - payload: web::Json, + payload: web::Json, data: web::Data, id: Identity, ) -> ServiceResult { is_authenticated(&id)?; + let username = id.identity().unwrap(); let duration = sqlx::query_as!( GetDurationResp, - "SELECT duration FROM mcaptcha_config WHERE - name = $1;", - &payload.token, + "SELECT duration 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?; @@ -100,8 +105,6 @@ mod tests { const NAME: &str = "testuserduration"; const PASSWORD: &str = "longpassworddomain"; const EMAIL: &str = "testuserduration@a.com"; - const DOMAIN: &str = "http://duration.example.com"; - const TOKEN_NAME: &str = "duration_routes_token"; const GET_URL: &str = "/api/v1/mcaptcha/domain/token/duration/get"; const UPDATE_URL: &str = "/api/v1/mcaptcha/domain/token/duration/update"; @@ -111,24 +114,20 @@ mod tests { } register_and_signin(NAME, EMAIL, PASSWORD).await; - let (data, _, signin_resp) = add_token_util(NAME, PASSWORD, DOMAIN, TOKEN_NAME).await; + let (data, _, signin_resp, token_key) = add_token_util(NAME, PASSWORD).await; let cookies = get_cookie!(signin_resp); let mut app = get_app!(data).await; let update = UpdateDuration { - token_name: TOKEN_NAME.into(), + key: token_key.key.clone(), duration: 40, }; - let get = GetDuration { - token: TOKEN_NAME.into(), - }; - // check default let get_level_resp = test::call_service( &mut app, - post_request!(&get, GET_URL) + post_request!(&token_key, GET_URL) .cookie(cookies.clone()) .to_request(), ) @@ -149,7 +148,7 @@ mod tests { assert_eq!(update_duration.status(), StatusCode::OK); let get_level_resp = test::call_service( &mut app, - post_request!(&get, GET_URL) + post_request!(&token_key, GET_URL) .cookie(cookies.clone()) .to_request(), ) diff --git a/src/api/v1/mcaptcha/levels.rs b/src/api/v1/mcaptcha/levels.rs index 894f75bf..83f66c8f 100644 --- a/src/api/v1/mcaptcha/levels.rs +++ b/src/api/v1/mcaptcha/levels.rs @@ -21,6 +21,7 @@ use m_captcha::{defense::Level, DefenseBuilder}; use serde::{Deserialize, Serialize}; use super::is_authenticated; +use crate::api::v1::mcaptcha::mcaptcha::MCaptchaDetails; use crate::errors::*; use crate::Data; @@ -28,12 +29,12 @@ use crate::Data; pub struct AddLevels { pub levels: Vec, // name is config_name - pub name: String, + pub key: String, } // TODO try for non-existent token names -#[post("/api/v1/mcaptcha/domain/token/levels/add")] +#[post("/api/v1/mcaptcha/levels/add")] pub async fn add_levels( payload: web::Json, data: web::Data, @@ -41,6 +42,7 @@ pub async fn add_levels( ) -> ServiceResult { is_authenticated(&id)?; let mut defense = DefenseBuilder::default(); + let username = id.identity().unwrap(); for level in payload.levels.iter() { defense.add_level(level.clone())?; @@ -55,10 +57,16 @@ pub async fn add_levels( "INSERT INTO mcaptcha_levels ( difficulty_factor, visitor_threshold, - config_id) VALUES ($1, $2, (SELECT config_id FROM mcaptcha_config WHERE name = ($3) ));", + config_id) VALUES ( + $1, $2, ( + SELECT config_id FROM mcaptcha_config WHERE + key = ($3) AND user_id = ( + SELECT ID FROM mcaptcha_users WHERE name = $4 + )));", difficulty_factor, visitor_threshold, - &payload.name, + &payload.key, + &username, ) .execute(&data.db) .await?; @@ -67,13 +75,14 @@ pub async fn add_levels( Ok(HttpResponse::Ok()) } -#[post("/api/v1/mcaptcha/domain/token/levels/update")] +#[post("/api/v1/mcaptcha/levels/update")] pub async fn update_levels( payload: web::Json, data: web::Data, id: Identity, ) -> ServiceResult { is_authenticated(&id)?; + let username = id.identity().unwrap(); let mut defense = DefenseBuilder::default(); for level in payload.levels.iter() { @@ -88,9 +97,13 @@ pub async fn update_levels( sqlx::query!( "DELETE FROM mcaptcha_levels WHERE config_id = ( - SELECT config_id FROM mcaptcha_config where name = ($1) + SELECT config_id FROM mcaptcha_config where key = ($1) + AND user_id = ( + SELECT ID from mcaptcha_users WHERE name = $2 + ) )", - &payload.name, + &payload.key, + &username ) .execute(&data.db) .await?; @@ -102,10 +115,17 @@ pub async fn update_levels( "INSERT INTO mcaptcha_levels ( difficulty_factor, visitor_threshold, - config_id) VALUES ($1, $2, (SELECT config_id FROM mcaptcha_config WHERE name = ($3) ));", + config_id) VALUES ( + $1, $2, ( + SELECT config_id FROM mcaptcha_config WHERE key = ($3) AND + user_id = ( + SELECT ID from mcaptcha_users WHERE name = $4 + ) + ));", difficulty_factor, visitor_threshold, - &payload.name, + &payload.key, + &username, ) .execute(&data.db) .await?; @@ -114,23 +134,26 @@ pub async fn update_levels( Ok(HttpResponse::Ok()) } -#[post("/api/v1/mcaptcha/domain/token/levels/delete")] +#[post("/api/v1/mcaptcha/levels/delete")] pub async fn delete_levels( payload: web::Json, data: web::Data, id: Identity, ) -> ServiceResult { is_authenticated(&id)?; + let username = id.identity().unwrap(); for level in payload.levels.iter() { let difficulty_factor = level.difficulty_factor as i32; sqlx::query!( "DELETE FROM mcaptcha_levels WHERE config_id = ( - SELECT config_id FROM mcaptcha_config WHERE name = ($1) + SELECT config_id FROM mcaptcha_config WHERE key = $1 AND + user_id = (SELECT ID from mcaptcha_users WHERE name = $3) ) AND difficulty_factor = ($2);", - &payload.name, + &payload.key, difficulty_factor, + &username ) .execute(&data.db) .await?; @@ -139,20 +162,16 @@ pub async fn delete_levels( Ok(HttpResponse::Ok()) } -#[derive(Deserialize, Serialize)] -pub struct GetLevels { - pub token: String, -} - -#[post("/api/v1/mcaptcha/domain/token/levels/get")] +#[post("/api/v1/mcaptcha/levels/get")] pub async fn get_levels( - payload: web::Json, + payload: web::Json, data: web::Data, id: Identity, ) -> ServiceResult { is_authenticated(&id)?; + let username = id.identity().unwrap(); - let levels = get_levels_util(&payload.token, &data).await?; + let levels = get_levels_util(&payload.key, &username, &data).await?; Ok(HttpResponse::Ok().json(levels)) } @@ -168,14 +187,16 @@ pub struct I32Levels { visitor_threshold: i32, } -async fn get_levels_util(name: &str, data: &Data) -> ServiceResult> { +async fn get_levels_util(key: &str, username: &str, data: &Data) -> ServiceResult> { let levels = sqlx::query_as!( I32Levels, "SELECT difficulty_factor, visitor_threshold FROM mcaptcha_levels WHERE config_id = ( - SELECT config_id FROM mcaptcha_config WHERE name = ($1) + SELECT config_id FROM mcaptcha_config WHERE key = ($1) + AND user_id = (SELECT ID from mcaptcha_users WHERE name = $2) );", - name + key, + &username ) .fetch_all(&data.db) .await?; @@ -198,12 +219,10 @@ mod tests { const NAME: &str = "testuserlevelroutes"; const PASSWORD: &str = "longpassworddomain"; const EMAIL: &str = "testuserlevelrouts@a.com"; - const DOMAIN: &str = "http://level.example.com"; - const TOKEN_NAME: &str = "level_routes_work"; - const ADD_URL: &str = "/api/v1/mcaptcha/domain/token/levels/add"; - const UPDATE_URL: &str = "/api/v1/mcaptcha/domain/token/levels/update"; - const DEL_URL: &str = "/api/v1/mcaptcha/domain/token/levels/delete"; - const GET_URL: &str = "/api/v1/mcaptcha/domain/token/levels/get"; + const ADD_URL: &str = "/api/v1/mcaptcha/levels/add"; + const UPDATE_URL: &str = "/api/v1/mcaptcha/levels/update"; + const DEL_URL: &str = "/api/v1/mcaptcha/levels/delete"; + const GET_URL: &str = "/api/v1/mcaptcha/levels/get"; let l1 = Level { difficulty_factor: 50, @@ -214,14 +233,6 @@ mod tests { visitor_threshold: 500, }; let levels = vec![l1, l2]; - let add_level = AddLevels { - levels: levels.clone(), - name: TOKEN_NAME.into(), - }; - - let get_level = GetLevels { - token: TOKEN_NAME.into(), - }; { let data = Data::new().await; @@ -229,10 +240,15 @@ mod tests { } register_and_signin(NAME, EMAIL, PASSWORD).await; - let (data, _, signin_resp) = add_token_util(NAME, PASSWORD, DOMAIN, TOKEN_NAME).await; + let (data, _, signin_resp, key) = add_token_util(NAME, PASSWORD).await; let cookies = get_cookie!(signin_resp); let mut app = get_app!(data).await; + let add_level = AddLevels { + levels: levels.clone(), + key: key.key.clone(), + }; + // 1. add level let add_token_resp = test::call_service( &mut app, @@ -246,7 +262,7 @@ mod tests { // 2. get level let get_level_resp = test::call_service( &mut app, - post_request!(&get_level, GET_URL) + post_request!(&key, GET_URL) .cookie(cookies.clone()) .to_request(), ) @@ -268,7 +284,7 @@ mod tests { let levels = vec![l1, l2]; let add_level = AddLevels { levels: levels.clone(), - name: TOKEN_NAME.into(), + key: key.key.clone(), }; let add_token_resp = test::call_service( &mut app, @@ -280,7 +296,7 @@ mod tests { assert_eq!(add_token_resp.status(), StatusCode::OK); let get_level_resp = test::call_service( &mut app, - post_request!(&get_level, GET_URL) + post_request!(&key, GET_URL) .cookie(cookies.clone()) .to_request(), ) @@ -301,7 +317,7 @@ mod tests { let levels = vec![l1, l2]; let add_level = AddLevels { levels: levels.clone(), - name: TOKEN_NAME.into(), + key: key.key.clone(), }; let add_token_resp = test::call_service( &mut app, @@ -313,7 +329,7 @@ mod tests { assert_eq!(add_token_resp.status(), StatusCode::OK); let get_level_resp = test::call_service( &mut app, - post_request!(&get_level, GET_URL) + post_request!(&key, GET_URL) .cookie(cookies.clone()) .to_request(), ) diff --git a/src/api/v1/mcaptcha/mcaptcha.rs b/src/api/v1/mcaptcha/mcaptcha.rs index 9e7709b2..888849af 100644 --- a/src/api/v1/mcaptcha/mcaptcha.rs +++ b/src/api/v1/mcaptcha/mcaptcha.rs @@ -18,7 +18,6 @@ use actix_identity::Identity; use actix_web::{post, web, HttpResponse, Responder}; use serde::{Deserialize, Serialize}; -use url::Url; use super::{get_random, is_authenticated}; use crate::errors::*; @@ -26,68 +25,58 @@ use crate::Data; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct MCaptchaID { - pub name: String, - pub domain: String, + pub name: Option, } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct MCaptchaDetails { - pub name: String, + pub name: Option, pub key: String, } -#[post("/api/v1/mcaptcha/domain/token/add")] -pub async fn add_mcaptcha( - payload: web::Json, - data: web::Data, - id: Identity, -) -> ServiceResult { +#[post("/api/v1/mcaptcha/add")] +pub async fn add_mcaptcha(data: web::Data, id: Identity) -> ServiceResult { is_authenticated(&id)?; + let username = id.identity().unwrap(); let key = get_random(32); - let url = Url::parse(&payload.domain)?; - let host = url.host_str().ok_or(ServiceError::NotAUrl)?; let res = sqlx::query!( "INSERT INTO mcaptcha_config - (name, key, domain_name) - VALUES ($1, $2, ( - SELECT name FROM mcaptcha_domains_verified WHERE name = ($3)))", - &payload.name, + (key, user_id) + VALUES ($1, (SELECT ID FROM mcaptcha_users WHERE name = $2))", &key, - &host, + &username, ) .execute(&data.db) .await; match res { - Err(e) => Err(dup_error(e, ServiceError::TokenNameTaken)), + Err(e) => { + println!("{}", &e); + Err(dup_error(e, ServiceError::TokenNameTaken)) + } Ok(_) => { - let resp = MCaptchaDetails { - key, - name: payload.into_inner().name, - }; - + let resp = MCaptchaDetails { key, name: None }; Ok(HttpResponse::Ok().json(resp)) } } } -#[post("/api/v1/mcaptcha/domain/token/update")] +#[post("/api/v1/mcaptcha/update/key")] pub async fn update_token( - payload: web::Json, + payload: web::Json, data: web::Data, id: Identity, ) -> ServiceResult { use std::borrow::Cow; is_authenticated(&id)?; - let url = Url::parse(&payload.domain)?; + let username = id.identity().unwrap(); let mut key; - let host = url.host_str().ok_or(ServiceError::NotAUrl)?; loop { key = get_random(32); - let res = update_token_helper(&key, &payload.name, &host, &data).await; + let res = update_token_helper(&key, &payload.key, &username, &data).await; if res.is_ok() { break; } else { @@ -111,37 +100,36 @@ pub async fn update_token( async fn update_token_helper( key: &str, - name: &str, - host: &str, + old_key: &str, + username: &str, data: &Data, ) -> Result<(), sqlx::Error> { sqlx::query!( "UPDATE mcaptcha_config SET key = $1 - WHERE name = $2 AND domain_name = $3", + WHERE key = $2 AND user_id = (SELECT ID FROM mcaptcha_users WHERE name = $3)", &key, - &name, - &host, + &old_key, + &username, ) .execute(&data.db) .await?; Ok(()) } -#[post("/api/v1/mcaptcha/domain/token/get")] +#[post("/api/v1/mcaptcha/get")] pub async fn get_token( - payload: web::Json, + payload: web::Json, data: web::Data, id: Identity, ) -> ServiceResult { is_authenticated(&id)?; - let url = Url::parse(&payload.domain)?; - - let host = url.host_str().ok_or(ServiceError::NotAUrl)?; + let username = id.identity().unwrap(); let res = match sqlx::query_as!( MCaptchaDetails, - "SELECT key, name from mcaptcha_config WHERE name = $1 AND domain_name = $2", - &payload.name, - &host, + "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 @@ -157,16 +145,20 @@ pub async fn get_token( Ok(HttpResponse::Ok().json(res)) } -#[post("/api/v1/mcaptcha/domain/token/delete")] +#[post("/api/v1/mcaptcha/delete")] pub async fn delete_mcaptcha( - payload: web::Json, + payload: web::Json, data: web::Data, id: Identity, ) -> ServiceResult { is_authenticated(&id)?; + let username = id.identity().unwrap(); + sqlx::query!( - "DELETE FROM mcaptcha_config WHERE name = ($1)", - &payload.name, + "DELETE FROM mcaptcha_config + WHERE key = ($1) AND user_id = (SELECT ID FROM mcaptcha_users WHERE name = $2) ", + &payload.key, + &username, ) .execute(&data.db) .await?; @@ -197,10 +189,8 @@ mod tests { const NAME: &str = "testusermcaptcha"; const PASSWORD: &str = "longpassworddomain"; const EMAIL: &str = "testusermcaptcha@a.com"; - const DOMAIN: &str = "http://mcaptcha.example.com"; - const TOKEN_NAME: &str = "add_mcaptcha_works_token"; - const ADD_URL: &str = "/api/v1/mcaptcha/domain/token/add"; - const DEL_URL: &str = "/api/v1/mcaptcha/domain/token/delete"; + const ADD_URL: &str = "/api/v1/mcaptcha/add"; + const DEL_URL: &str = "/api/v1/mcaptcha/delete"; { let data = Data::new().await; @@ -209,42 +199,18 @@ mod tests { // 1. add mcaptcha token register_and_signin(NAME, EMAIL, PASSWORD).await; - let (data, _, signin_resp) = add_token_util(NAME, PASSWORD, DOMAIN, TOKEN_NAME).await; + let (data, _, signin_resp, token_key) = add_token_util(NAME, PASSWORD).await; let cookies = get_cookie!(signin_resp); let mut app = get_app!(data).await; - let mut domain = MCaptchaID { - domain: DOMAIN.into(), - name: TOKEN_NAME.into(), - }; - - // 2. add duplicate mcaptha - bad_post_req_test( - NAME, - PASSWORD, - ADD_URL, - &domain, - ServiceError::TokenNameTaken, - StatusCode::BAD_REQUEST, - ) - .await; - - // 4. not a URL test for adding domain - domain.domain = "testing".into(); - bad_post_req_test( - NAME, - PASSWORD, - ADD_URL, - &domain, - ServiceError::NotAUrl, - StatusCode::BAD_REQUEST, - ) - .await; + // let mut domain = MCaptchaID { + // name: TOKEN_NAME.into(), + // }; // 4. delete token let del_token = test::call_service( &mut app, - post_request!(&domain, DEL_URL) + post_request!(&token_key, DEL_URL) .cookie(cookies.clone()) .to_request(), ) @@ -257,10 +223,8 @@ mod tests { const NAME: &str = "updateusermcaptcha"; const PASSWORD: &str = "longpassworddomain"; const EMAIL: &str = "testupdateusermcaptcha@a.com"; - const DOMAIN: &str = "http://update-mcaptcha.example.com"; - const TOKEN_NAME: &str = "get_update_mcaptcha_works_token"; - const UPDATE_URL: &str = "/api/v1/mcaptcha/domain/token/update"; - const GET_URL: &str = "/api/v1/mcaptcha/domain/token/get"; + const UPDATE_URL: &str = "/api/v1/mcaptcha/update/key"; + const GET_URL: &str = "/api/v1/mcaptcha/get"; { let data = Data::new().await; @@ -269,18 +233,14 @@ mod tests { // 1. add mcaptcha token register_and_signin(NAME, EMAIL, PASSWORD).await; - let (data, _, signin_resp) = add_token_util(NAME, PASSWORD, DOMAIN, TOKEN_NAME).await; + let (data, _, signin_resp, token_key) = add_token_util(NAME, PASSWORD).await; let cookies = get_cookie!(signin_resp); let mut app = get_app!(data).await; - let mut domain = MCaptchaID { - domain: DOMAIN.into(), - name: TOKEN_NAME.into(), - }; - + // 2. update token key let update_token_resp = test::call_service( &mut app, - post_request!(&domain, UPDATE_URL) + post_request!(&token_key, UPDATE_URL) .cookie(cookies.clone()) .to_request(), ) @@ -288,22 +248,25 @@ mod tests { assert_eq!(update_token_resp.status(), StatusCode::OK); let updated_token: MCaptchaDetails = test::read_body_json(update_token_resp).await; + // get token key with updated key let get_token_resp = test::call_service( &mut app, - post_request!(&domain, GET_URL) + post_request!(&updated_token, GET_URL) .cookie(cookies.clone()) .to_request(), ) .await; assert_eq!(get_token_resp.status(), StatusCode::OK); - let get_token_key: MCaptchaDetails = test::read_body_json(get_token_resp).await; + + // 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); - domain.name = "https://batsense.net".into(); + get_token_key.key = "nonexistent".into(); let get_nonexistent_token_resp = test::call_service( &mut app, - post_request!(&domain, GET_URL) + post_request!(&get_token_key, GET_URL) .cookie(cookies.clone()) .to_request(), ) diff --git a/src/api/v1/mcaptcha/mod.rs b/src/api/v1/mcaptcha/mod.rs index 5d5f981f..192ac035 100644 --- a/src/api/v1/mcaptcha/mod.rs +++ b/src/api/v1/mcaptcha/mod.rs @@ -15,7 +15,6 @@ * along with this program. If not, see . */ -pub mod domains; pub mod duration; pub mod levels; pub mod mcaptcha; diff --git a/src/api/v1/mod.rs b/src/api/v1/mod.rs index 86a07b6b..a66f1e05 100644 --- a/src/api/v1/mod.rs +++ b/src/api/v1/mod.rs @@ -34,13 +34,6 @@ pub fn services(cfg: &mut ServiceConfig) { cfg.service(auth::username_exists); cfg.service(auth::email_exists); - // mcaptcha - // domain - cfg.service(mcaptcha::domains::add_domain); - cfg.service(mcaptcha::domains::delete_domain); - cfg.service(mcaptcha::domains::verify); - cfg.service(mcaptcha::domains::get_challenge); - // mcaptcha cfg.service(mcaptcha::mcaptcha::add_mcaptcha); cfg.service(mcaptcha::mcaptcha::delete_mcaptcha); diff --git a/src/api/v1/tests/kvserver.rs b/src/api/v1/tests/kvserver.rs deleted file mode 100644 index 75810161..00000000 --- a/src/api/v1/tests/kvserver.rs +++ /dev/null @@ -1,77 +0,0 @@ -/* -* 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 log::info; -use std::collections::HashMap; -use std::sync::mpsc; -use std::sync::{Arc, RwLock}; - -use actix_web::{dev::Server, middleware, web, App, HttpResponse, HttpServer, Responder}; -use serde::{Deserialize, Serialize}; - -/* - * Simple KV Server that stores a json of with schema - * `Challenge` at path /{key}/ on POST and emits on GET - */ - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Challenge { - verification_challenge: String, -} - -pub async fn server(ip: &str, tx: mpsc::Sender) { - pretty_env_logger::init(); - let srv = HttpServer::new(move || { - let store: UtilKVServer = Arc::new(RwLock::new(HashMap::new())); - App::new() - .wrap(middleware::Logger::default()) - .wrap(middleware::NormalizePath::default()) - .data(store) - .route("/{key}/", web::post().to(util_server_add)) - .route("/{key}/", web::get().to(util_server_retrive)) - }) - .bind(ip) - .unwrap() - .run(); - - tx.send(srv.clone()).unwrap(); -} - -type UtilKVServer = Arc>>; - -#[cfg(not(tarpaulin_include))] -async fn util_server_retrive( - key: web::Path, - data: web::Data, -) -> impl Responder { - let key = key.into_inner(); - let store = data.read().unwrap(); - let resp = store.get(&key).unwrap(); - info!("[kv-server] retrive: key :{}, value: {:?}", key, resp); - HttpResponse::Ok().json(resp) -} - -#[cfg(not(tarpaulin_include))] -async fn util_server_add( - key: web::Path, - payload: web::Json, - data: web::Data, -) -> impl Responder { - info!("[kv-server] cache: key :{}, value: {:?}", key, payload); - let mut store = data.write().unwrap(); - store.insert(key.into_inner(), payload.into_inner()); - HttpResponse::Ok() -} diff --git a/src/api/v1/tests/mod.rs b/src/api/v1/tests/mod.rs index e24152c2..edcf8801 100644 --- a/src/api/v1/tests/mod.rs +++ b/src/api/v1/tests/mod.rs @@ -16,6 +16,3 @@ */ mod auth; -mod kvserver; - -pub use kvserver::server; diff --git a/src/tests/mod.rs b/src/tests/mod.rs index df7fafef..4e1403d8 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -7,6 +7,7 @@ use serde::Serialize; use super::*; use crate::api::v1::auth::{Login, Register}; +use crate::api::v1::mcaptcha::mcaptcha::MCaptchaDetails; use crate::api::v1::services as v1_services; use crate::data::Data; use crate::errors::*; @@ -30,6 +31,10 @@ pub async fn delete_user(name: &str, data: &Data) { #[macro_export] macro_rules! post_request { + ($uri:expr) => { + test::TestRequest::post().uri($uri) + }; + ($serializable:expr, $uri:expr) => { test::TestRequest::post() .uri($uri) @@ -96,79 +101,81 @@ pub async fn signin<'a>(name: &'a str, password: &str) -> (data::Data, Login, Se (data, creds, signin_resp) } -/// register and signin and domain -/// bypasses domain verification, use with care -pub async fn add_domain_util( +///// register and signin and domain +///// bypasses domain verification, use with care +//pub async fn add_domain_util( +// name: &str, +// password: &str, +// domain: &str, +//) -> (data::Data, Login, ServiceResponse) { +// use crate::api::v1::mcaptcha::domains::Domain; +// use url::Url; +// +// let (data, creds, signin_resp) = signin(name, password).await; +// let cookies = get_cookie!(signin_resp); +// let mut app = get_app!(data).await; +// +// // 1. add domain +// let add_domain = Domain { +// name: domain.into(), +// }; +// +// let add_domain_resp = test::call_service( +// &mut app, +// post_request!(&add_domain, "/api/v1/mcaptcha/domain/add") +// .cookie(cookies.clone()) +// .to_request(), +// ) +// .await; +// assert_eq!(add_domain_resp.status(), StatusCode::OK); +// +// // verification work around +// let url = Url::parse(domain).unwrap(); +// let host = url.host_str().unwrap(); +// sqlx::query!( +// "INSERT INTO mcaptcha_domains_verified (name, owner_id) VALUES +// ($1, (SELECT ID from mcaptcha_users WHERE name = $2))", +// &host, +// &name +// ) +// .execute(&data.db) +// .await +// .unwrap(); +// +// (data, creds, signin_resp) +//} + +pub async fn add_token_util( name: &str, password: &str, - domain: &str, -) -> (data::Data, Login, ServiceResponse) { - use crate::api::v1::mcaptcha::domains::Domain; - use url::Url; +) -> (data::Data, Login, ServiceResponse, MCaptchaDetails) { + // use crate::api::v1::mcaptcha::mcaptcha::MCaptchaID; + + const ADD_URL: &str = "/api/v1/mcaptcha/add"; let (data, creds, signin_resp) = signin(name, password).await; let cookies = get_cookie!(signin_resp); let mut app = get_app!(data).await; - // 1. add domain - let add_domain = Domain { - name: domain.into(), - }; - - let add_domain_resp = test::call_service( - &mut app, - post_request!(&add_domain, "/api/v1/mcaptcha/domain/add") - .cookie(cookies.clone()) - .to_request(), - ) - .await; - assert_eq!(add_domain_resp.status(), StatusCode::OK); - - // verification work around - let url = Url::parse(domain).unwrap(); - let host = url.host_str().unwrap(); - sqlx::query!( - "INSERT INTO mcaptcha_domains_verified (name, owner_id) VALUES - ($1, (SELECT ID from mcaptcha_users WHERE name = $2))", - &host, - &name - ) - .execute(&data.db) - .await - .unwrap(); - - (data, creds, signin_resp) -} - -pub async fn add_token_util( - name: &str, - password: &str, - domain: &str, - token_name: &str, -) -> (data::Data, Login, ServiceResponse) { - use crate::api::v1::mcaptcha::mcaptcha::MCaptchaID; - - const ADD_URL: &str = "/api/v1/mcaptcha/domain/token/add"; - - let (data, creds, signin_resp) = add_domain_util(name, password, domain).await; - let cookies = get_cookie!(signin_resp); - let mut app = get_app!(data).await; - - // 1. add mcaptcha token - let domain = MCaptchaID { - domain: domain.into(), - name: token_name.into(), - }; + // // 1. add mcaptcha token + // let domain = MCaptchaID { + // name: token_name.into(), + // }; let add_token_resp = test::call_service( &mut app, - post_request!(&domain, ADD_URL) - .cookie(cookies.clone()) - .to_request(), + post_request!(ADD_URL).cookie(cookies.clone()).to_request(), ) .await; + // let status = add_token_resp.status(); + // let txt: ErrorToResponse = test::read_body_json(add_token_resp).await; + // println!("{:?}", txt.error); + // assert_eq!(add_token_resp.status(), StatusCode::OK); + let token_key: MCaptchaDetails = test::read_body_json(add_token_resp).await; - (data, creds, signin_resp) + // assert_eq!(status, StatusCode::OK); + + (data, creds, signin_resp, token_key) } /// pub duplicate test