diff --git a/Cargo.lock b/Cargo.lock index df8d458d..af860d44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -521,11 +521,10 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bincode" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d175dfa69e619905c4c3cdb7c3c203fa3bdd5d51184e3afdb2742c0280493772" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "byteorder", "serde 1.0.125", ] @@ -601,9 +600,9 @@ checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" [[package]] name = "byteorder" -version = "1.3.4" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" @@ -629,7 +628,7 @@ dependencies = [ [[package]] name = "cache-buster" version = "0.1.0" -source = "git+https://github.com/realaravinth/cache-buster#26d40b9993820837aa9d2d683937e502bfd3be12" +source = "git+https://github.com/realaravinth/cache-buster#71d5ef67a2788789922eaa484e10269acbaeb8a7" dependencies = [ "data-encoding", "derive_builder 0.10.0", @@ -1295,6 +1294,7 @@ dependencies = [ "m_captcha", "mime", "mime_guess", + "pow_sha256", "pretty_env_logger", "rand 0.8.3", "rust-embed", @@ -1611,7 +1611,7 @@ dependencies = [ [[package]] name = "m_captcha" version = "0.1.3" -source = "git+https://github.com/mCaptcha/mCaptcha?branch=master#2d120d6791d8a32e7b3e31a7c5f1b54e3b35f9ef" +source = "git+https://github.com/mCaptcha/mCaptcha?branch=master#29cd8f4fd83a3646a48ca2c9f5563d8d5360d2c3" dependencies = [ "actix", "derive_builder 0.9.0", diff --git a/Cargo.toml b/Cargo.toml index 9f7dbec2..16d8c431 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,3 +67,6 @@ serde_json = "1" yaml-rust = "0.4.5" cache-buster = { version = "0.1", git = "https://github.com/realaravinth/cache-buster" } mime = "0.3.16" + +[dev-dependencies] +pow_sha256 = { version = "0.2.1", git = "https://github.com/mcaptcha/pow_sha256" } diff --git a/build.rs b/build.rs index 1199bdf0..e14b6343 100644 --- a/build.rs +++ b/build.rs @@ -55,6 +55,5 @@ fn cache_bust() { .build() .unwrap(); - config.init().unwrap(); - config.hash().unwrap().to_env(); + config.process().unwrap().to_env(); } diff --git a/src/api/v1/mcaptcha/pow.rs b/src/api/v1/mcaptcha/pow/get_config.rs similarity index 97% rename from src/api/v1/mcaptcha/pow.rs rename to src/api/v1/mcaptcha/pow/get_config.rs index 497bde02..5bbed715 100644 --- a/src/api/v1/mcaptcha/pow.rs +++ b/src/api/v1/mcaptcha/pow/get_config.rs @@ -16,14 +16,12 @@ */ use actix::prelude::*; -use actix_identity::Identity; use actix_web::{post, web, HttpResponse, Responder}; use m_captcha::{defense::LevelBuilder, master::AddSiteBuilder, DefenseBuilder, MCaptchaBuilder}; use serde::{Deserialize, Serialize}; -use super::duration::GetDurationResp; -use super::is_authenticated; -use super::levels::I32Levels; +use super::GetDurationResp; +use super::I32Levels; use crate::errors::*; use crate::Data; @@ -44,10 +42,7 @@ pub struct GetConfigPayload { pub async fn get_config( payload: web::Json, data: web::Data, - id: Identity, ) -> ServiceResult { - is_authenticated(&id)?; - let res = sqlx::query!( "SELECT EXISTS (SELECT 1 from mcaptcha_config WHERE key = $1)", &payload.key, diff --git a/src/api/v1/mcaptcha/pow/mod.rs b/src/api/v1/mcaptcha/pow/mod.rs new file mode 100644 index 00000000..364c0864 --- /dev/null +++ b/src/api/v1/mcaptcha/pow/mod.rs @@ -0,0 +1,23 @@ +/* +* 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 . +*/ + +pub mod get_config; +pub mod verify_pow; + +pub use super::duration::GetDurationResp; +pub use super::is_authenticated; +pub use super::levels::I32Levels; diff --git a/src/api/v1/mcaptcha/pow/verifiy_token.rs b/src/api/v1/mcaptcha/pow/verifiy_token.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/api/v1/mcaptcha/pow/verify_pow.rs b/src/api/v1/mcaptcha/pow/verify_pow.rs new file mode 100644 index 00000000..f5051ef7 --- /dev/null +++ b/src/api/v1/mcaptcha/pow/verify_pow.rs @@ -0,0 +1,135 @@ +/* +* 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::{post, web, HttpResponse, Responder}; +use m_captcha::pow::Work; +use serde::{Deserialize, Serialize}; + +use crate::errors::*; +use crate::Data; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct PoWConfig { + pub name: String, + pub domain: String, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ValidationToken { + pub token: String, +} + +// API keys are mcaptcha actor names + +#[post("/api/v1/mcaptcha/pow/verify")] +pub async fn verify_pow( + payload: web::Json, + data: web::Data, +) -> ServiceResult { + let res = data.captcha.verify_pow(payload.into_inner()).await?; + let payload = ValidationToken { token: res }; + Ok(HttpResponse::Ok().json(payload)) +} + +#[cfg(test)] +mod tests { + use actix_web::http::{header, StatusCode}; + use actix_web::test; + use m_captcha::pow::PoWConfig; + + use super::*; + use crate::api::v1::mcaptcha::pow::get_config::GetConfigPayload; + use crate::api::v1::services as v1_services; + use crate::tests::*; + use crate::*; + + #[actix_rt::test] + async fn verify_pow_works() { + const NAME: &str = "powverifyusr"; + const PASSWORD: &str = "testingpas"; + const EMAIL: &str = "verifyuser@a.com"; + const VERIFY_URL: &str = "/api/v1/mcaptcha/pow/verify"; + const GET_URL: &str = "/api/v1/mcaptcha/pow/config"; + // const UPDATE_URL: &str = "/api/v1/mcaptcha/domain/token/duration/update"; + + { + let data = Data::new().await; + delete_user(NAME, &data).await; + } + + register_and_signin(NAME, EMAIL, PASSWORD).await; + let (data, _, _signin_resp, token_key) = add_levels_util(NAME, PASSWORD).await; + let mut app = get_app!(data).await; + + let get_config_payload = GetConfigPayload { + key: token_key.key.clone(), + }; + + // update and check changes + + let get_config_resp = test::call_service( + &mut app, + post_request!(&get_config_payload, GET_URL).to_request(), + ) + .await; + assert_eq!(get_config_resp.status(), StatusCode::OK); + let config: PoWConfig = test::read_body_json(get_config_resp).await; + + let pow = pow_sha256::ConfigBuilder::default() + .salt(config.salt) + .build() + .unwrap(); + let work = pow + .prove_work(&config.string.clone(), config.difficulty_factor) + .unwrap(); + + let work = Work { + string: config.string.clone(), + result: work.result, + nonce: work.nonce, + key: token_key.key.clone(), + }; + + let pow_verify_resp = + test::call_service(&mut app, post_request!(&work, VERIFY_URL).to_request()).await; + assert_eq!(pow_verify_resp.status(), StatusCode::OK); + + let string_not_found = + test::call_service(&mut app, post_request!(&work, VERIFY_URL).to_request()).await; + assert_eq!(string_not_found.status(), StatusCode::BAD_REQUEST); + let err: ErrorToResponse = test::read_body_json(string_not_found).await; + assert_eq!( + err.error, + format!( + "{}", + ServiceError::CaptchaError(m_captcha::errors::CaptchaError::StringNotFound) + ) + ); + + let pow_config_resp = test::call_service( + &mut app, + post_request!(&get_config_payload, GET_URL).to_request(), + ) + .await; + assert_eq!(pow_config_resp.status(), StatusCode::OK); + // I'm not checking for errors because changing work.result triggered + // InssuficientDifficulty, which is possible becuase m_captcha calculates + // difficulty with the submitted result. Besides, this endpoint is merely + // propagating errors from m_captcha and m_captcha has tests covering the + // pow aspects ¯\_(ツ)_/¯ + } +} diff --git a/src/api/v1/mod.rs b/src/api/v1/mod.rs index a66f1e05..3d818b1b 100644 --- a/src/api/v1/mod.rs +++ b/src/api/v1/mod.rs @@ -51,7 +51,8 @@ pub fn services(cfg: &mut ServiceConfig) { cfg.service(mcaptcha::duration::get_duration); // pow - cfg.service(mcaptcha::pow::get_config); + cfg.service(mcaptcha::pow::get_config::get_config); + cfg.service(mcaptcha::pow::verify_pow::verify_pow); } #[cfg(test)] diff --git a/src/errors.rs b/src/errors.rs index a7e77453..e573334c 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -76,9 +76,6 @@ pub enum ServiceError { /// when the a token name is already taken #[display(fmt = "token name not available")] TokenNameTaken, - /// when the a host name is already taken - #[display(fmt = "host name not available")] - HostnameTaken, /// token not found #[display(fmt = "Token not found. Is token registered?")] TokenNotFound, @@ -88,10 +85,6 @@ pub enum ServiceError { #[display(fmt = "Couldn't reach your server. If Problem presists, contact support")] ClientServerUnreachable, - #[display(fmt = "Couldn't parse challenge from your server. Check for courruption")] - ChallengeCourruption, - #[display(fmt = "Verification failure, vaules didn't match")] - ChallengeVerificationFailure, } #[derive(Serialize, Deserialize)] @@ -132,10 +125,7 @@ impl ResponseError for ServiceError { ServiceError::TokenNameTaken => StatusCode::BAD_REQUEST, ServiceError::TokenNotFound => StatusCode::NOT_FOUND, - ServiceError::HostnameTaken => StatusCode::BAD_REQUEST, ServiceError::ClientServerUnreachable => StatusCode::SERVICE_UNAVAILABLE, - ServiceError::ChallengeCourruption => StatusCode::BAD_REQUEST, - ServiceError::ChallengeVerificationFailure => StatusCode::UNAUTHORIZED, ServiceError::CaptchaError(e) => match e { CaptchaError::MailboxError => StatusCode::INTERNAL_SERVER_ERROR, _ => StatusCode::BAD_REQUEST,