mockforge_ui/auth/
password_policy.rs1use std::collections::HashSet;
7
8#[derive(Debug, Clone)]
10pub struct PasswordPolicy {
11 pub min_length: usize,
13 pub max_length: usize,
15 pub require_uppercase: bool,
17 pub require_lowercase: bool,
19 pub require_numbers: bool,
21 pub require_special: bool,
23 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, forbidden_passwords: Self::common_passwords(),
37 }
38 }
39}
40
41impl PasswordPolicy {
42 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 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 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 pub fn validate(
83 &self,
84 password: &str,
85 username: Option<&str>,
86 ) -> Result<(), PasswordValidationError> {
87 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 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 let password_lower = password.to_lowercase();
116 if self.forbidden_passwords.contains(&password_lower) {
117 return Err(PasswordValidationError::CommonPassword);
118 }
119
120 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#[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 {}