// Copyright (C) 2022 Aravinth Manivannan // SPDX-FileCopyrightText: 2023 Aravinth Manivannan // // SPDX-License-Identifier: AGPL-3.0-or-later #![warn(missing_docs)] //! # `mCaptcha` database operations //! //! Traits and datastructures used in mCaptcha to interact with database. //! //! To use an unsupported database with mCaptcha, traits present within this crate should be //! implemented. //! //! //! ## Organisation //! //! Database functionality is divided across various modules: //! //! - [errors](crate::auth): error data structures used in this crate //! - [ops](crate::ops): meta operations like connection pool creation, migrations and getting //! connection from pool use serde::{Deserialize, Serialize}; pub use libmcaptcha::defense::Level; pub mod errors; pub mod ops; #[cfg(feature = "test")] pub mod tests; use dev::*; pub use ops::GetConnection; pub mod prelude { //! useful imports for users working with a supported database pub use super::errors::*; pub use super::ops::*; pub use super::*; } pub mod dev { //! useful imports for supporting a new database pub use super::prelude::*; pub use async_trait::async_trait; } #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] /// Data required to register a new user pub struct Register<'a> { /// username of new user pub username: &'a str, /// secret of new user pub secret: &'a str, /// hashed password of new use pub hash: &'a str, /// Optionally, email of new use pub email: Option<&'a str>, } #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] /// data required to update them email of a user pub struct UpdateEmail<'a> { /// username of the user pub username: &'a str, /// new email address of the user pub new_email: &'a str, } #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] /// types of credentials used as identifiers during login pub enum Login<'a> { /// username as login Username(&'a str), /// email as login Email(&'a str), } #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] /// type encapsulating username and hashed password of a user pub struct NameHash { /// username pub username: String, /// hashed password pub hash: String, } #[async_trait] /// mCaptcha's database requirements. To implement support for $Database, kindly implement this /// trait. pub trait MCDatabase: std::marker::Send + std::marker::Sync + CloneSPDatabase { /// ping DB async fn ping(&self) -> bool; /// register a new user async fn register(&self, p: &Register) -> DBResult<()>; /// delete a user async fn delete_user(&self, username: &str) -> DBResult<()>; /// check if username exists async fn username_exists(&self, username: &str) -> DBResult; /// get user email async fn get_email(&self, username: &str) -> DBResult>; /// check if email exists async fn email_exists(&self, email: &str) -> DBResult; /// update a user's email async fn update_email(&self, p: &UpdateEmail) -> DBResult<()>; /// get a user's password async fn get_password(&self, l: &Login) -> DBResult; /// update user's password async fn update_password(&self, p: &NameHash) -> DBResult<()>; /// update username async fn update_username(&self, current: &str, new: &str) -> DBResult<()>; /// get a user's secret async fn get_secret(&self, username: &str) -> DBResult; /// get a user's secret from a captcha key async fn get_secret_from_captcha(&self, key: &str) -> DBResult; /// update a user's secret async fn update_secret(&self, username: &str, secret: &str) -> DBResult<()>; /// create new captcha async fn create_captcha(&self, username: &str, p: &CreateCaptcha) -> DBResult<()>; /// Get captcha config async fn get_captcha_config(&self, username: &str, key: &str) -> DBResult; /// Get all captchas belonging to user async fn get_all_user_captchas(&self, username: &str) -> DBResult>; /// update captcha metadata; doesn't change captcha key async fn update_captcha_metadata( &self, username: &str, p: &CreateCaptcha, ) -> DBResult<()>; /// update captcha key; doesn't change metadata async fn update_captcha_key( &self, username: &str, old_key: &str, new_key: &str, ) -> DBResult<()>; /// Add levels to captcha async fn add_captcha_levels( &self, username: &str, captcha_key: &str, levels: &[Level], ) -> DBResult<()>; /// check if captcha exists async fn captcha_exists( &self, username: Option<&str>, captcha_key: &str, ) -> DBResult; /// Delete all levels of a captcha async fn delete_captcha_levels( &self, username: &str, captcha_key: &str, ) -> DBResult<()>; /// Delete captcha async fn delete_captcha(&self, username: &str, captcha_key: &str) -> DBResult<()>; /// Get captcha levels async fn get_captcha_levels( &self, username: Option<&str>, captcha_key: &str, ) -> DBResult>; /// Get captcha's cooldown period async fn get_captcha_cooldown(&self, captcha_key: &str) -> DBResult; /// Add traffic configuration async fn add_traffic_pattern( &self, username: &str, captcha_key: &str, pattern: &TrafficPattern, ) -> DBResult<()>; /// Get traffic configuration async fn get_traffic_pattern( &self, username: &str, captcha_key: &str, ) -> DBResult; /// Get all easy captcha configurations on instance async fn get_all_easy_captchas( &self, limit: usize, offset: usize, ) -> DBResult>; /// Delete traffic configuration async fn delete_traffic_pattern( &self, username: &str, captcha_key: &str, ) -> DBResult<()>; /// create new notification async fn create_notification(&self, p: &AddNotification) -> DBResult<()>; /// get all unread notifications async fn get_all_unread_notifications( &self, username: &str, ) -> DBResult>; /// mark a notification read async fn mark_notification_read(&self, username: &str, id: i32) -> DBResult<()>; /// record PoWConfig fetches async fn record_fetch(&self, key: &str) -> DBResult<()>; /// record PoWConfig solves async fn record_solve(&self, key: &str) -> DBResult<()>; /// record PoWConfig confirms async fn record_confirm(&self, key: &str) -> DBResult<()>; /// fetch PoWConfig fetches async fn fetch_config_fetched(&self, user: &str, key: &str) -> DBResult>; /// fetch PoWConfig solves async fn fetch_solve(&self, user: &str, key: &str) -> DBResult>; /// fetch PoWConfig confirms async fn fetch_confirm(&self, user: &str, key: &str) -> DBResult>; /// record PoW timing async fn analysis_save( &self, captcha_id: &str, d: &CreatePerformanceAnalytics, ) -> DBResult<()>; /// fetch PoW analytics async fn analytics_fetch( &self, captcha_id: &str, limit: usize, offset: usize, ) -> DBResult>; /// Create psuedo ID against campaign ID to publish analytics async fn analytics_create_psuedo_id_if_not_exists( &self, captcha_id: &str, ) -> DBResult<()>; /// Get psuedo ID from campaign ID async fn analytics_get_psuedo_id_from_capmaign_id( &self, captcha_id: &str, ) -> DBResult; /// Get campaign ID from psuedo ID async fn analytics_get_capmaign_id_from_psuedo_id( &self, psuedo_id: &str, ) -> DBResult; /// Delete all records for campaign async fn analytics_delete_all_records_for_campaign( &self, campaign_id: &str, ) -> DBResult<()>; /// Get publishing status of pow analytics for captcha ID/ campaign ID async fn analytics_captcha_is_published(&self, campaign_id: &str) -> DBResult { match self .analytics_get_psuedo_id_from_capmaign_id(campaign_id) .await { Ok(_) => Ok(true), Err(errors::DBError::CaptchaNotFound) => Ok(false), Err(e) => Err(e), } } /// Get all psuedo IDs async fn analytics_get_all_psuedo_ids(&self, page: usize) -> DBResult>; /// Track maximum nonce received against captcha levels async fn update_max_nonce_for_level( &self, captcha_key: &str, difficulty_factor: u32, latest_nonce: u32, ) -> DBResult<()>; /// Get maximum nonce tracked so far for captcha levels async fn get_max_nonce_for_level( &self, captcha_key: &str, difficulty_factor: u32, ) -> DBResult; /// Get number of analytics entries that are under a certain duration async fn stats_get_num_logs_under_time(&self, duration: u32) -> DBResult; /// Get the entry at a location in the list of analytics entires under a certain time limit /// and sorted in ascending order async fn stats_get_entry_at_location_for_time_limit_asc( &self, duration: u32, location: u32, ) -> DBResult>; } #[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)] /// Log Proof-of-Work CAPTCHA performance analytics pub struct CreatePerformanceAnalytics { /// 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, } #[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, } #[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)] /// Captcha statistics with time recorded in UNIX epoch formats pub struct StatsUnixTimestamp { /// times at which the configuration were fetched pub config_fetches: Vec, /// times at which the PoW was solved pub solves: Vec, /// times at which the PoW token was verified pub confirms: Vec, } #[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)] /// Represents notification pub struct Notification { /// receiver name of the notification pub name: Option, /// heading of the notification pub heading: Option, /// message of the notification pub message: Option, /// when notification was received pub received: Option, /// db assigned ID of the notification pub id: Option, } #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] /// Data required to add notification pub struct AddNotification<'a> { /// who is the notification addressed to? pub to: &'a str, /// notification sender pub from: &'a str, /// heading of the notification pub heading: &'a str, /// message of the notification pub message: &'a str, } #[derive(Default, PartialEq, Serialize, Deserialize, Clone, Debug)] /// Represents Easy captcha configuration pub struct EasyCaptcha { /// traffic pattern of easy captcha pub traffic_pattern: TrafficPattern, /// captcha key/sitekey pub key: String, /// captcha description pub description: String, /// Owner of the captcha configuration pub username: String, } #[derive(Default, PartialEq, Serialize, Deserialize, Clone, Debug)] /// User's traffic pattern; used in generating a captcha configuration pub struct TrafficPattern { /// average traffic of user's website pub avg_traffic: u32, /// the peak traffic that the user's website can handle pub peak_sustainable_traffic: u32, /// traffic that bought the user's website down; optional pub broke_my_site_traffic: Option, } #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] /// data required to create new captcha pub struct CreateCaptcha<'a> { /// cool down duration pub duration: i32, /// description of the captcha pub description: &'a str, /// secret key of the captcha pub key: &'a str, } #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] /// Data representing a captcha pub struct Captcha { /// Database assigned ID pub config_id: i32, /// cool down duration pub duration: i32, /// description of the captcha pub description: String, /// secret key of the captcha pub key: String, } #[derive(Clone, Debug, Deserialize, PartialEq, Default, Serialize)] /// datastructure representing a user's secret pub struct Secret { /// user's secret pub secret: String, } /// Trait to clone MCDatabase pub trait CloneSPDatabase { /// clone DB fn clone_db(&self) -> Box; } impl CloneSPDatabase for T where T: MCDatabase + Clone + 'static, { fn clone_db(&self) -> Box { Box::new(self.clone()) } } impl Clone for Box { fn clone(&self) -> Self { (**self).clone_db() } }