Skip to content

Commit

Permalink
feat!: [#591] extract new type for password constrains in configuration
Browse files Browse the repository at this point in the history
From:

```toml
[auth]
email_on_signup = "Optional"
max_password_length = 64
min_password_length = 6
secret_key = "MaxVerstappenWC2021"
```

To:

```toml
[auth]
email_on_signup = "Optional"
secret_key = "MaxVerstappenWC2021"

[auth.password_constraints]
max_password_length = 64
min_password_length = 6
```
  • Loading branch information
josecelano committed Jun 12, 2024
1 parent 50ebb9a commit 7124d58
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 44 deletions.
7 changes: 5 additions & 2 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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_";
Expand Down Expand Up @@ -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"
Expand Down
55 changes: 40 additions & 15 deletions src/config/v1/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
}
Expand All @@ -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.
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
21 changes: 8 additions & 13 deletions src/services/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
&registration_form.password,
&registration_form.confirm_password,
&password_constraints,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
23 changes: 18 additions & 5 deletions src/web/api/client/v1/contexts/settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -135,9 +140,17 @@ impl From<DomainAuth> 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<DomainPasswordConstraints> 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,
}
}
}
Expand Down
5 changes: 2 additions & 3 deletions src/web/api/server/v1/contexts/user/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
23 changes: 18 additions & 5 deletions tests/common/contexts/settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -149,9 +154,17 @@ impl From<DomainAuth> 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<DomainPasswordConstraints> 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,
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions tests/e2e/web/api/v1/contexts/settings/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ async fn it_should_allow_admins_to_get_all_the_settings() {

let response = client.get_settings().await;

println!("{}", response.body);

let res: AllSettingsResponse = serde_json::from_str(&response.body).unwrap();

assert_eq!(res.data, env.server_settings_masking_secrets().unwrap());
Expand Down

0 comments on commit 7124d58

Please sign in to comment.