From cdbf6788f009c9f6e391e1d93dc635e27b0b6a1a Mon Sep 17 00:00:00 2001 From: Aravinth Manivannan Date: Sun, 2 Jul 2023 00:19:04 +0530 Subject: [PATCH] feat: HTTP client to talk to mCaptcha/mCaptcha --- src/data.rs | 9 +- src/main.rs | 6 +- src/mcaptcha.rs | 201 +++++++++++++++++++++++++++++++++++++++++ src/pages/auth/join.rs | 3 +- src/tests.rs | 16 +++- 5 files changed, 231 insertions(+), 4 deletions(-) create mode 100644 src/mcaptcha.rs diff --git a/src/data.rs b/src/data.rs index 7bb9231..0444525 100644 --- a/src/data.rs +++ b/src/data.rs @@ -22,6 +22,7 @@ use argon2_creds::{Config, ConfigBuilder, PasswordPolicy}; use sqlx::postgres::PgPoolOptions; use sqlx::PgPool; +use crate::mcaptcha::*; use crate::settings::Settings; /// App data @@ -30,6 +31,8 @@ pub struct Data { pub db: PgPool, pub creds: Config, pub settings: Settings, + + pub mcaptcha: Box, } impl Data { @@ -45,7 +48,10 @@ impl Data { #[cfg(not(tarpaulin_include))] /// create new instance of app data - pub async fn new(settings: Settings) -> Arc { + pub async fn new( + settings: Settings, + mcaptcha: Box, + ) -> Arc { let creds = Self::get_creds(); let c = creds.clone(); #[allow(unused_variables)] @@ -67,6 +73,7 @@ impl Data { db, creds, settings, + mcaptcha, }; Arc::new(data) diff --git a/src/main.rs b/src/main.rs index 47ce40c..3a6ee72 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,6 +31,7 @@ mod api; mod archive; mod data; mod errors; +mod mcaptcha; mod pages; mod settings; mod static_assets; @@ -86,7 +87,10 @@ async fn main() -> std::io::Result<()> { ); let settings = Settings::new().unwrap(); - let data = Data::new(settings.clone()).await; + let mcaptcha: Box = + Box::new(mcaptcha::MCaptchaClientReqwest::default()); + + let data = Data::new(settings.clone(), mcaptcha).await; sqlx::migrate!("./migrations/").run(&data.db).await.unwrap(); let data = actix_web::web::Data::new(data); diff --git a/src/mcaptcha.rs b/src/mcaptcha.rs new file mode 100644 index 0000000..d0a61e0 --- /dev/null +++ b/src/mcaptcha.rs @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2023 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 async_trait::async_trait; +use reqwest::Client; +use serde::{Deserialize, Serialize}; +use url::Url; + +use crate::errors::*; + +/* TODO: + * 1. Define traits to interact with mCaptcha + * 2. Implement trait with request 3. Implement mocking for testing + * 4. Load to crate::data::Data + */ + +#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)] +/// Proof-of-Work CAPTCHA performance analytics +pub struct PerformanceAnalytics { + /// log ID + pub id: usize, + /// time taken to generate proof + pub time: u32, + /// difficulty factor for which the proof was generated + pub difficulty_factor: u32, + /// worker/client type: wasm, javascript, python, etc. + pub worker_type: String, +} + +#[async_trait] +pub trait MCaptchaClient: + std::marker::Send + std::marker::Sync + CloneMCaptchaClient +{ + async fn share_secret( + &self, + mut mcaptcha: Url, + secret: &Secret, + ) -> ServiceResult<()>; + async fn download_benchmarks( + &self, + mut mcaptcha: Url, + campaign_id: &str, + page: usize, + ) -> ServiceResult>; +} + +/// Trait to clone MCaptchaClient +pub trait CloneMCaptchaClient { + /// clone client + fn clone_client(&self) -> Box; +} + +impl CloneMCaptchaClient for T +where + T: MCaptchaClient + Clone + 'static, +{ + fn clone_client(&self) -> Box { + Box::new(self.clone()) + } +} + +impl Clone for Box { + fn clone(&self) -> Self { + (**self).clone_client() + } +} + +#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)] +pub struct Secret { + pub secret: String, +} + +#[derive(Clone)] +pub struct MCaptchaClientReqwest { + client: Client, +} + +impl Default for MCaptchaClientReqwest { + fn default() -> Self { + Self { + client: Client::new(), + } + } +} + +#[async_trait] +impl MCaptchaClient for MCaptchaClientReqwest { + async fn share_secret( + &self, + mut mcaptcha: Url, + secret: &Secret, + ) -> ServiceResult<()> { + mcaptcha.set_path("/api/v1/survey/secret"); + self.client + .post(mcaptcha) + .json(secret) + .send() + .await + .unwrap(); + Ok(()) + } + async fn download_benchmarks( + &self, + mut mcaptcha: Url, + campaign_id: &str, + page: usize, + ) -> ServiceResult> { + mcaptcha.set_path(&format!("/api/v1/survey/{campaign_id}/get?page={page}")); + let res = self + .client + .get(mcaptcha) + .send() + .await + .unwrap() + .json() + .await + .unwrap(); + Ok(res) + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + use std::collections::HashMap; + use std::sync::{Arc, RwLock}; + + use lazy_static::lazy_static; + + lazy_static! { + pub static ref BENCHMARK: Vec = vec![ + PerformanceAnalytics { + id: 1, + time: 2, + difficulty_factor: 3, + worker_type: "foo".to_string(), + }, + PerformanceAnalytics { + id: 4, + time: 5, + difficulty_factor: 6, + worker_type: "bar".to_string(), + }, + ]; + } + + #[derive(Clone)] + pub struct TestClient { + pub client: Arc>>, + } + + impl Default for TestClient { + fn default() -> Self { + Self { + client: Arc::new(RwLock::new(HashMap::default())), + } + } + } + + #[async_trait] + impl MCaptchaClient for TestClient { + async fn share_secret( + &self, + mut mcaptcha: Url, + secret: &Secret, + ) -> ServiceResult<()> { + mcaptcha.set_path("/api/v1/survey/secret"); + let mut x = self.client.write().unwrap(); + x.insert(mcaptcha.to_string(), secret.secret.to_owned()); + drop(x); + Ok(()) + } + async fn download_benchmarks( + &self, + mcaptcha: Url, + campaign_id: &str, + page: usize, + ) -> ServiceResult> { + println!( + "mcaptcha_url {}, campaign_id {}, page: {page}", + mcaptcha.to_string(), + campaign_id + ); + let res = BENCHMARK.clone(); + Ok(res) + } + } +} diff --git a/src/pages/auth/join.rs b/src/pages/auth/join.rs index 21baf06..ff58c8e 100644 --- a/src/pages/auth/join.rs +++ b/src/pages/auth/join.rs @@ -109,7 +109,8 @@ mod tests { #[actix_rt::test] async fn auth_join_form_works() { let settings = Settings::new().unwrap(); - let data = Data::new(settings).await; + // let data = Data::new(settings).await; + let data = get_test_data().await; const NAME: &str = "testuserformjoin"; const NAME2: &str = "testuserformjoin2"; const EMAIL: &str = "testuserformjoin@a.com"; diff --git a/src/tests.rs b/src/tests.rs index 7721e44..286b8d0 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -41,12 +41,26 @@ use crate::data::Data; use crate::errors::*; use crate::V1_API_ROUTES; +pub async fn get_test_data_with_mcaptcha_client( +) -> (Arc, crate::mcaptcha::tests::TestClient) { + let mut settings = Settings::new().unwrap(); + let tmp_dir = Temp::new_dir().unwrap(); + settings.publish.dir = tmp_dir.join("base_path").to_str().unwrap().into(); + settings.allow_registration = true; + let test_mcaptcha = crate::mcaptcha::tests::TestClient::default(); + ( + Data::new(settings, Box::new(test_mcaptcha.clone())).await, + test_mcaptcha, + ) +} + pub async fn get_test_data() -> Arc { let mut settings = Settings::new().unwrap(); let tmp_dir = Temp::new_dir().unwrap(); settings.publish.dir = tmp_dir.join("base_path").to_str().unwrap().into(); settings.allow_registration = true; - Data::new(settings).await + let test_mcaptcha = Box::new(crate::mcaptcha::tests::TestClient::default()); + Data::new(settings, test_mcaptcha).await } #[macro_export]