diff --git a/src/data.rs b/src/data.rs index 98779937..23cf1896 100644 --- a/src/data.rs +++ b/src/data.rs @@ -4,8 +4,8 @@ // SPDX-License-Identifier: AGPL-3.0-or-later //! App data: redis cache, database connections, etc. -use std::sync::{RwLock, Arc}; use std::collections::HashMap; +use std::sync::Arc; use std::thread; use std::time::Duration; @@ -31,15 +31,16 @@ use libmcaptcha::{ system::{System, SystemBuilder}, }; use reqwest::Client; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; use tokio::task::JoinHandle; use tokio::time::sleep; -use crate::AppData; use crate::db::{self, BoxDB}; use crate::errors::ServiceResult; use crate::settings::Settings; use crate::stats::{Dummy, Real, Stats}; +use crate::survey::SecretsStore; +use crate::AppData; macro_rules! enum_system_actor { ($name:ident, $type:ident) => { @@ -173,6 +174,8 @@ pub struct Data { pub settings: Settings, /// stats recorder pub stats: Box, + /// survey secret store + pub survey_secrets: SecretsStore, } impl Data { @@ -187,7 +190,7 @@ impl Data { } #[cfg(not(tarpaulin_include))] /// create new instance of app data - pub async fn new(s: &Settings) -> Arc { + pub async fn new(s: &Settings, survey_secrets: SecretsStore) -> Arc { let creds = Self::get_creds(); let c = creds.clone(); @@ -216,6 +219,7 @@ impl Data { mailer: Self::get_mailer(s), settings: s.clone(), stats, + survey_secrets, }; #[cfg(not(debug_assertions))] @@ -258,87 +262,5 @@ impl Data { } } -#[async_trait::async_trait] -trait SurveyClientTrait { - async fn start_job(&self, data: AppData) -> ServiceResult>; - async fn register(&self, data: &AppData) -> ServiceResult<()>; -} - -#[derive(Clone, Debug, Default)] -struct SecretsStore { - store: Arc>> -} - -impl SecretsStore { - fn get(&self, key: &str) -> Option { - let r = self.store.read().unwrap(); - r.get(key).map(|x| x.to_owned()) - } - - fn set(&self, key: String, value: String) { - let mut w = self.store.write().unwrap(); - w.insert(key,value ); - drop(w); - } -} - - - -struct Survey { - settings: Settings, - client: Client, - secrets: SecretsStore, -} -impl Survey { - fn new(settings: Settings) -> Self { - Survey { - client: Client::new(), - settings, - secrets: SecretsStore::default(), - } - } -} - -#[async_trait::async_trait] -impl SurveyClientTrait for Survey { - async fn start_job(&self, data: AppData) -> ServiceResult> { - let fut = async move { - loop { - sleep(Duration::new(data.settings.survey.rate_limit, 0)).await; -// if let Err(e) = Self::delete_demo_user(&data).await { -// log::error!("Error while deleting demo user: {:?}", e); -// } -// if let Err(e) = Self::register_demo_user(&data).await { -// log::error!("Error while registering demo user: {:?}", e); -// } - } - }; - let handle = tokio::spawn(fut); - Ok(handle) - - } - async fn register(&self, data: &AppData) -> ServiceResult<()> { - let protocol = if self.settings.server.proxy_has_tls { - "https://" - } else { - "http://" - }; - #[derive(Serialize)] - struct MCaptchaInstance { - url: url::Url, - } - - let payload = MCaptchaInstance { - url: url::Url::parse(&format!("{protocol}{}", self.settings.server.domain))?, - }; - for url in self.settings.survey.nodes.iter() { - self.client.post(url.clone()).json(&payload).send().await.unwrap(); - } - Ok(()) - } - - -} - /// Mailer data type AsyncSmtpTransport pub type Mailer = AsyncSmtpTransport; diff --git a/src/main.rs b/src/main.rs index 2c44c8fd..55650eda 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,6 +30,7 @@ mod routes; mod settings; mod static_assets; mod stats; +mod survey; #[cfg(test)] #[macro_use] mod tests; @@ -104,7 +105,8 @@ async fn main() -> std::io::Result<()> { ); let settings = Settings::new().unwrap(); - let data = Data::new(&settings).await; + let secrets = survey::SecretsStore::default(); + let data = Data::new(&settings, secrets).await; let data = actix_web::web::Data::new(data); let mut demo_user: Option = None; diff --git a/src/survey.rs b/src/survey.rs new file mode 100644 index 00000000..243d7ce9 --- /dev/null +++ b/src/survey.rs @@ -0,0 +1,121 @@ +// Copyright (C) 2022 Aravinth Manivannan +// SPDX-FileCopyrightText: 2023 Aravinth Manivannan +// +// SPDX-License-Identifier: AGPL-3.0-or-later +use std::collections::HashMap; +use std::sync::Arc; +use std::sync::RwLock; +use std::time::Duration; + +use reqwest::Client; +use serde::{Deserialize, Serialize}; +use tokio::task::JoinHandle; +use tokio::time::sleep; + +use crate::errors::*; +use crate::settings::Settings; +use crate::AppData; + +#[async_trait::async_trait] +trait SurveyClientTrait { + async fn start_job(&self, data: AppData) -> ServiceResult>; + async fn register(&self, data: &AppData) -> ServiceResult<()>; +} + +#[derive(Clone, Debug, Default)] +pub struct SecretsStore { + store: Arc>>, +} + +impl SecretsStore { + pub fn get(&self, key: &str) -> Option { + let r = self.store.read().unwrap(); + r.get(key).map(|x| x.to_owned()) + } + + pub fn rm(&self, key: &str) { + let mut w = self.store.write().unwrap(); + w.remove(key); + drop(w); + } + + pub fn set(&self, key: String, value: String) { + let mut w = self.store.write().unwrap(); + w.insert(key, value); + drop(w); + } +} + +struct Survey { + settings: Settings, + client: Client, + secrets: SecretsStore, +} +impl Survey { + fn new(settings: Settings, secrets: SecretsStore) -> Self { + Survey { + client: Client::new(), + settings, + secrets, + } + } +} + +#[async_trait::async_trait] +impl SurveyClientTrait for Survey { + async fn start_job(&self, data: AppData) -> ServiceResult> { + let fut = async move { + loop { + sleep(Duration::new(data.settings.survey.rate_limit, 0)).await; + // if let Err(e) = Self::delete_demo_user(&data).await { + // log::error!("Error while deleting demo user: {:?}", e); + // } + // if let Err(e) = Self::register_demo_user(&data).await { + // log::error!("Error while registering demo user: {:?}", e); + // } + } + }; + let handle = tokio::spawn(fut); + Ok(handle) + } + async fn register(&self, data: &AppData) -> ServiceResult<()> { + let protocol = if self.settings.server.proxy_has_tls { + "https://" + } else { + "http://" + }; + #[derive(Serialize)] + struct MCaptchaInstance { + url: url::Url, + secret: String, + } + + let this_instance_url = + url::Url::parse(&format!("{protocol}{}", self.settings.server.domain))?; + for url in self.settings.survey.nodes.iter() { + // mCaptcha/survey must send this token while uploading secret to authenticate itself + // this token must be sent to mCaptcha/survey with the registration payload + let secret_upload_auth_token = crate::api::v1::mcaptcha::get_random(20); + + let payload = MCaptchaInstance { + url: this_instance_url.clone(), + secret: secret_upload_auth_token.clone(), + }; + + // SecretsStore will store auth tokens generated by both mCaptcha/mCaptcha and + // mCaptcha/survey + // + // Storage schema: + // - mCaptcha/mCaptcha generated auth token: (, ) + // - mCaptcha/survey generated auth token (, Settings { @@ -30,6 +31,7 @@ pub mod pg { use crate::data::Data; use crate::settings::*; + use crate::survey::SecretsStore; use crate::ArcData; use super::get_settings; @@ -42,7 +44,7 @@ pub mod pg { settings.database.database_type = DBType::Postgres; settings.database.pool = 2; - Data::new(&settings).await + Data::new(&settings, SecretsStore::default()).await } } pub mod maria { @@ -50,6 +52,7 @@ pub mod maria { use crate::data::Data; use crate::settings::*; + use crate::survey::SecretsStore; use crate::ArcData; use super::get_settings; @@ -62,7 +65,7 @@ pub mod maria { settings.database.database_type = DBType::Maria; settings.database.pool = 2; - Data::new(&settings).await + Data::new(&settings, SecretsStore::default()).await } } //pub async fn get_data() -> ArcData { @@ -181,6 +184,26 @@ pub async fn signin( (creds, signin_resp) } +/// pub duplicate test +pub async fn bad_post_req_test_no_auth( + data: &ArcData, + url: &str, + payload: &T, + err: ServiceError, +) { + let app = get_app!(data).await; + + let resp = test::call_service(&app, post_request!(&payload, url).to_request()).await; + if resp.status() != err.status_code() { + let resp_err: ErrorToResponse = test::read_body_json(resp).await; + panic!("error {}", resp_err.error); + } + assert_eq!(resp.status(), err.status_code()); + let resp_err: ErrorToResponse = test::read_body_json(resp).await; + //println!("{}", txt.error); + assert_eq!(resp_err.error, format!("{}", err)); +} + /// pub duplicate test pub async fn bad_post_req_test( data: &ArcData,