diff --git a/src/config/mod.rs b/src/config/mod.rs index 275732da..1bc8b407 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -30,6 +30,7 @@ pub type Tracker = v1::tracker::Tracker; pub type Logging = v1::logging::Logging; pub type Website = v1::website::Website; pub type EmailOnSignup = v1::auth::EmailOnSignup; +pub type PasswordConstraints = v1::auth::PasswordConstraints; /// Prefix for env vars that overwrite configuration options. const CONFIG_OVERRIDE_PREFIX: &str = "TORRUST_INDEX_CONFIG_OVERRIDE_"; @@ -344,10 +345,12 @@ mod tests { [auth] email_on_signup = "optional" - min_password_length = 6 - max_password_length = 64 secret_key = "MaxVerstappenWC2021" + [auth.password_constraints] + min_password_length = 6 + max_password_length = 64 + [database] connect_url = "sqlite://data.db?mode=rwc" diff --git a/src/config/v1/auth.rs b/src/config/v1/auth.rs index 0a685757..007d3084 100644 --- a/src/config/v1/auth.rs +++ b/src/config/v1/auth.rs @@ -7,25 +7,21 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Auth { /// Whether or not to require an email on signup. - #[serde(default = "EmailOnSignup::default")] + #[serde(default = "Auth::default_email_on_signup")] pub email_on_signup: EmailOnSignup, - /// The minimum password length. - #[serde(default = "Auth::default_min_password_length")] - pub min_password_length: usize, - /// The maximum password length. - #[serde(default = "Auth::default_max_password_length")] - pub max_password_length: usize, /// The secret key used to sign JWT tokens. #[serde(default = "Auth::default_secret_key")] pub secret_key: SecretKey, + /// The password constraints + #[serde(default = "Auth::default_password_constraints")] + pub password_constraints: PasswordConstraints, } impl Default for Auth { fn default() -> Self { Self { email_on_signup: EmailOnSignup::default(), - min_password_length: Self::default_min_password_length(), - max_password_length: Self::default_max_password_length(), + password_constraints: Self::default_password_constraints(), secret_key: Self::default_secret_key(), } } @@ -36,17 +32,17 @@ impl Auth { self.secret_key = SecretKey::new(secret_key); } - fn default_min_password_length() -> usize { - 6 - } - - fn default_max_password_length() -> usize { - 64 + fn default_email_on_signup() -> EmailOnSignup { + EmailOnSignup::default() } fn default_secret_key() -> SecretKey { SecretKey::new("MaxVerstappenWC2021") } + + fn default_password_constraints() -> PasswordConstraints { + PasswordConstraints::default() + } } /// Whether the email is required on signup or not. @@ -119,6 +115,35 @@ impl fmt::Display for SecretKey { } } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct PasswordConstraints { + /// The minimum password length. + #[serde(default = "PasswordConstraints::default_min_password_length")] + pub min_password_length: usize, + /// The maximum password length. + #[serde(default = "PasswordConstraints::default_max_password_length")] + pub max_password_length: usize, +} + +impl Default for PasswordConstraints { + fn default() -> Self { + Self { + min_password_length: Self::default_min_password_length(), + max_password_length: Self::default_max_password_length(), + } + } +} + +impl PasswordConstraints { + fn default_min_password_length() -> usize { + 6 + } + + fn default_max_password_length() -> usize { + 64 + } +} + #[cfg(test)] mod tests { use super::SecretKey; diff --git a/src/lib.rs b/src/lib.rs index fa7fc351..0d7cd435 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -181,9 +181,11 @@ //! //! [auth] //! email_on_signup = "optional" +//! secret_key = "MaxVerstappenWC2021" +//! +//! [auth.password_constraints] //! min_password_length = 6 //! max_password_length = 64 -//! secret_key = "MaxVerstappenWC2021" //! //! [database] //! connect_url = "sqlite://data.db?mode=rwc" diff --git a/src/services/user.rs b/src/services/user.rs index 4d521029..6d0e4c4e 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -11,7 +11,7 @@ use pbkdf2::password_hash::rand_core::OsRng; use tracing::{debug, info}; use super::authentication::DbUserAuthenticationRepository; -use crate::config::{Configuration, EmailOnSignup}; +use crate::config::{Configuration, EmailOnSignup, PasswordConstraints}; use crate::databases::database::{Database, Error}; use crate::errors::ServiceError; use crate::mailer; @@ -97,11 +97,11 @@ impl RegistrationService { } let password_constraints = PasswordConstraints { - min_password_length: settings.auth.min_password_length, - max_password_length: settings.auth.max_password_length, + min_password_length: settings.auth.password_constraints.min_password_length, + max_password_length: settings.auth.password_constraints.max_password_length, }; - validate_password_constrains( + validate_password_constraints( ®istration_form.password, ®istration_form.confirm_password, &password_constraints, @@ -216,11 +216,11 @@ impl ProfileService { verify_password(change_password_form.current_password.as_bytes(), &user_authentication)?; let password_constraints = PasswordConstraints { - min_password_length: settings.auth.min_password_length, - max_password_length: settings.auth.max_password_length, + min_password_length: settings.auth.password_constraints.min_password_length, + max_password_length: settings.auth.password_constraints.max_password_length, }; - validate_password_constrains( + validate_password_constraints( &change_password_form.password, &change_password_form.confirm_password, &password_constraints, @@ -415,12 +415,7 @@ impl DbBannedUserList { } } -struct PasswordConstraints { - pub min_password_length: usize, - pub max_password_length: usize, -} - -fn validate_password_constrains( +fn validate_password_constraints( password: &str, confirm_password: &str, password_rules: &PasswordConstraints, diff --git a/src/web/api/client/v1/contexts/settings/mod.rs b/src/web/api/client/v1/contexts/settings/mod.rs index d762a98f..3a8ea60f 100644 --- a/src/web/api/client/v1/contexts/settings/mod.rs +++ b/src/web/api/client/v1/contexts/settings/mod.rs @@ -6,8 +6,8 @@ use url::Url; use crate::config::v1::tracker::ApiToken; use crate::config::{ Api as DomainApi, Auth as DomainAuth, Database as DomainDatabase, ImageCache as DomainImageCache, Mail as DomainMail, - Network as DomainNetwork, Settings as DomainSettings, Tracker as DomainTracker, - TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, + Network as DomainNetwork, PasswordConstraints as DomainPasswordConstraints, Settings as DomainSettings, + Tracker as DomainTracker, TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, }; #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] @@ -46,9 +46,14 @@ pub struct Network { #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Auth { pub email_on_signup: String, + pub secret_key: String, + pub password_constraints: PasswordConstraints, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct PasswordConstraints { pub min_password_length: usize, pub max_password_length: usize, - pub secret_key: String, } #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] @@ -135,9 +140,17 @@ impl From for Auth { fn from(auth: DomainAuth) -> Self { Self { email_on_signup: format!("{:?}", auth.email_on_signup), - min_password_length: auth.min_password_length, - max_password_length: auth.max_password_length, secret_key: auth.secret_key.to_string(), + password_constraints: auth.password_constraints.into(), + } + } +} + +impl From for PasswordConstraints { + fn from(password_constraints: DomainPasswordConstraints) -> Self { + Self { + min_password_length: password_constraints.min_password_length, + max_password_length: password_constraints.max_password_length, } } } diff --git a/src/web/api/server/v1/contexts/user/mod.rs b/src/web/api/server/v1/contexts/user/mod.rs index 2e3ce8d0..1d2eb2a3 100644 --- a/src/web/api/server/v1/contexts/user/mod.rs +++ b/src/web/api/server/v1/contexts/user/mod.rs @@ -45,9 +45,8 @@ //! //! ```toml //! [auth] -//! email_on_signup = "optional" -//! min_password_length = 6 -//! max_password_length = 64 +//! email_on_signup = "Optional" +//! secret_key = "MaxVerstappenWC2021" //! ``` //! //! Refer to the [`RegistrationForm`](crate::web::api::server::v1::contexts::user::forms::RegistrationForm) diff --git a/tests/common/contexts/settings/mod.rs b/tests/common/contexts/settings/mod.rs index 6e138f8d..74235bd2 100644 --- a/tests/common/contexts/settings/mod.rs +++ b/tests/common/contexts/settings/mod.rs @@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize}; use torrust_index::config::v1::tracker::ApiToken; use torrust_index::config::{ Api as DomainApi, Auth as DomainAuth, Database as DomainDatabase, ImageCache as DomainImageCache, Logging as DomainLogging, - Mail as DomainMail, Network as DomainNetwork, Settings as DomainSettings, Tracker as DomainTracker, - TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, + Mail as DomainMail, Network as DomainNetwork, PasswordConstraints as DomainPasswordConstraints, Settings as DomainSettings, + Tracker as DomainTracker, TrackerStatisticsImporter as DomainTrackerStatisticsImporter, Website as DomainWebsite, }; use url::Url; @@ -51,9 +51,14 @@ pub struct Network { #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] pub struct Auth { pub email_on_signup: String, + pub secret_key: String, + pub password_constraints: PasswordConstraints, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] +pub struct PasswordConstraints { pub min_password_length: usize, pub max_password_length: usize, - pub secret_key: String, } #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] @@ -149,9 +154,17 @@ impl From for Auth { fn from(auth: DomainAuth) -> Self { Self { email_on_signup: auth.email_on_signup.to_string(), - min_password_length: auth.min_password_length, - max_password_length: auth.max_password_length, secret_key: auth.secret_key.to_string(), + password_constraints: auth.password_constraints.into(), + } + } +} + +impl From for PasswordConstraints { + fn from(password_constraints: DomainPasswordConstraints) -> Self { + Self { + min_password_length: password_constraints.min_password_length, + max_password_length: password_constraints.max_password_length, } } }