mockforge_ui/auth/
password_policy.rs

1//! Password policy enforcement
2//!
3//! This module provides password validation and complexity requirements
4//! for user account creation and password changes.
5
6use std::collections::HashSet;
7
8/// Password policy configuration
9#[derive(Debug, Clone)]
10pub struct PasswordPolicy {
11    /// Minimum password length
12    pub min_length: usize,
13    /// Maximum password length
14    pub max_length: usize,
15    /// Require uppercase letters
16    pub require_uppercase: bool,
17    /// Require lowercase letters
18    pub require_lowercase: bool,
19    /// Require numbers
20    pub require_numbers: bool,
21    /// Require special characters
22    pub require_special: bool,
23    /// Forbidden passwords (common passwords, username, etc.)
24    pub forbidden_passwords: HashSet<String>,
25}
26
27impl Default for PasswordPolicy {
28    fn default() -> Self {
29        Self {
30            min_length: 8,
31            max_length: 128,
32            require_uppercase: true,
33            require_lowercase: true,
34            require_numbers: true,
35            require_special: false, // Optional for better UX
36            forbidden_passwords: Self::common_passwords(),
37        }
38    }
39}
40
41impl PasswordPolicy {
42    /// Create a strict password policy
43    pub fn strict() -> Self {
44        Self {
45            min_length: 12,
46            max_length: 128,
47            require_uppercase: true,
48            require_lowercase: true,
49            require_numbers: true,
50            require_special: true,
51            forbidden_passwords: Self::common_passwords(),
52        }
53    }
54
55    /// Create a relaxed password policy (for development)
56    pub fn relaxed() -> Self {
57        Self {
58            min_length: 6,
59            max_length: 128,
60            require_uppercase: false,
61            require_lowercase: true,
62            require_numbers: false,
63            require_special: false,
64            forbidden_passwords: HashSet::new(),
65        }
66    }
67
68    /// Common passwords to forbid
69    fn common_passwords() -> HashSet<String> {
70        [
71            "password", "123456", "12345678", "1234", "qwerty", "abc123", "monkey", "1234567",
72            "letmein", "trustno1", "dragon", "baseball", "iloveyou", "master", "sunshine",
73            "ashley", "bailey", "passw0rd", "shadow", "123123", "654321", "superman", "qazwsx",
74            "michael", "football",
75        ]
76        .iter()
77        .map(|s| s.to_string())
78        .collect()
79    }
80
81    /// Validate a password against the policy
82    pub fn validate(
83        &self,
84        password: &str,
85        username: Option<&str>,
86    ) -> Result<(), PasswordValidationError> {
87        // Check length
88        if password.len() < self.min_length {
89            return Err(PasswordValidationError::TooShort(self.min_length));
90        }
91        if password.len() > self.max_length {
92            return Err(PasswordValidationError::TooLong(self.max_length));
93        }
94
95        // Check character requirements
96        let has_uppercase = password.chars().any(|c| c.is_uppercase());
97        let has_lowercase = password.chars().any(|c| c.is_lowercase());
98        let has_number = password.chars().any(|c| c.is_ascii_digit());
99        let has_special = password.chars().any(|c| "!@#$%^&*()_+-=[]{}|;:,.<>?".contains(c));
100
101        if self.require_uppercase && !has_uppercase {
102            return Err(PasswordValidationError::MissingUppercase);
103        }
104        if self.require_lowercase && !has_lowercase {
105            return Err(PasswordValidationError::MissingLowercase);
106        }
107        if self.require_numbers && !has_number {
108            return Err(PasswordValidationError::MissingNumber);
109        }
110        if self.require_special && !has_special {
111            return Err(PasswordValidationError::MissingSpecial);
112        }
113
114        // Check forbidden passwords
115        let password_lower = password.to_lowercase();
116        if self.forbidden_passwords.contains(&password_lower) {
117            return Err(PasswordValidationError::CommonPassword);
118        }
119
120        // Check if password contains username
121        if let Some(username) = username {
122            if password_lower.contains(&username.to_lowercase()) {
123                return Err(PasswordValidationError::ContainsUsername);
124            }
125        }
126
127        Ok(())
128    }
129}
130
131/// Password validation errors
132#[derive(Debug, Clone, PartialEq, Eq)]
133pub enum PasswordValidationError {
134    TooShort(usize),
135    TooLong(usize),
136    MissingUppercase,
137    MissingLowercase,
138    MissingNumber,
139    MissingSpecial,
140    CommonPassword,
141    ContainsUsername,
142}
143
144impl std::fmt::Display for PasswordValidationError {
145    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146        match self {
147            PasswordValidationError::TooShort(min) => {
148                write!(f, "Password must be at least {} characters long", min)
149            }
150            PasswordValidationError::TooLong(max) => {
151                write!(f, "Password must be at most {} characters long", max)
152            }
153            PasswordValidationError::MissingUppercase => {
154                write!(f, "Password must contain at least one uppercase letter")
155            }
156            PasswordValidationError::MissingLowercase => {
157                write!(f, "Password must contain at least one lowercase letter")
158            }
159            PasswordValidationError::MissingNumber => {
160                write!(f, "Password must contain at least one number")
161            }
162            PasswordValidationError::MissingSpecial => {
163                write!(f, "Password must contain at least one special character")
164            }
165            PasswordValidationError::CommonPassword => {
166                write!(f, "Password is too common. Please choose a more unique password")
167            }
168            PasswordValidationError::ContainsUsername => {
169                write!(f, "Password cannot contain your username")
170            }
171        }
172    }
173}
174
175impl std::error::Error for PasswordValidationError {}