feat: load survey keystore
This commit is contained in:
parent
87785b38be
commit
f933a30e7e
4 changed files with 157 additions and 89 deletions
94
src/data.rs
94
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<dyn Stats>,
|
||||
/// 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<Self> {
|
||||
pub async fn new(s: &Settings, survey_secrets: SecretsStore) -> Arc<Self> {
|
||||
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<JoinHandle<()>>;
|
||||
async fn register(&self, data: &AppData) -> ServiceResult<()>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct SecretsStore {
|
||||
store: Arc<RwLock<HashMap<String, String>>>
|
||||
}
|
||||
|
||||
impl SecretsStore {
|
||||
fn get(&self, key: &str) -> Option<String> {
|
||||
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<JoinHandle<()>> {
|
||||
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<Tokio1Executor>
|
||||
pub type Mailer = AsyncSmtpTransport<Tokio1Executor>;
|
||||
|
|
|
@ -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<DemoUser> = None;
|
||||
|
|
121
src/survey.rs
Normal file
121
src/survey.rs
Normal file
|
@ -0,0 +1,121 @@
|
|||
// Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
//
|
||||
// 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<JoinHandle<()>>;
|
||||
async fn register(&self, data: &AppData) -> ServiceResult<()>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct SecretsStore {
|
||||
store: Arc<RwLock<HashMap<String, String>>>,
|
||||
}
|
||||
|
||||
impl SecretsStore {
|
||||
pub fn get(&self, key: &str) -> Option<String> {
|
||||
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<JoinHandle<()>> {
|
||||
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: (<auth_token>, <survey_instance_url>)
|
||||
// - mCaptcha/survey generated auth token (<survey_instance_url>, <auth_token)
|
||||
self.secrets.set(secret_upload_auth_token, url.to_string());
|
||||
self.client
|
||||
.post(url.clone())
|
||||
.json(&payload)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ use crate::api::v1::mcaptcha::create::CreateCaptcha;
|
|||
use crate::api::v1::mcaptcha::create::MCaptchaDetails;
|
||||
use crate::api::v1::ROUTES;
|
||||
use crate::errors::*;
|
||||
use crate::survey::SecretsStore;
|
||||
use crate::ArcData;
|
||||
|
||||
pub fn get_settings() -> 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<T: Serialize>(
|
||||
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<T: Serialize>(
|
||||
data: &ArcData,
|
||||
|
|
Loading…
Reference in a new issue