1use serde::{Deserialize, Serialize};
2
3pub mod timing_protection;
5
6pub mod secure_jwt;
8pub mod secure_mfa;
9pub mod secure_session;
10pub mod secure_session_config;
11pub mod secure_utils;
12
13pub mod presets;
15
16pub use presets::{
18 SecurityAuditReport, SecurityAuditStatus, SecurityIssue, SecurityPreset, SecuritySeverity,
19};
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct MfaConfig {
24 pub totp_config: TotpConfig,
26 pub sms_config: Option<SmsConfig>,
28 pub email_config: Option<EmailConfig>,
30 pub backup_codes_config: BackupCodesConfig,
32 pub challenge_timeout_seconds: u64,
34 pub max_verification_attempts: u32,
36}
37
38impl Default for MfaConfig {
39 fn default() -> Self {
40 Self {
41 totp_config: TotpConfig::default(),
42 sms_config: None,
43 email_config: None,
44 backup_codes_config: BackupCodesConfig::default(),
45 challenge_timeout_seconds: 300, max_verification_attempts: 3,
47 }
48 }
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct TotpConfig {
54 pub issuer: String,
56 pub digits: u8,
58 pub period: u64,
60 pub skew: u8,
62}
63
64impl Default for TotpConfig {
65 fn default() -> Self {
66 Self {
67 issuer: "Auth Framework".to_string(),
68 digits: 6,
69 period: 30,
70 skew: 1,
71 }
72 }
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct BackupCodesConfig {
78 pub count: usize,
80 pub length: usize,
82}
83
84impl Default for BackupCodesConfig {
85 fn default() -> Self {
86 Self {
87 count: 8,
88 length: 8,
89 }
90 }
91}
92
93#[derive(Debug, Clone, PartialEq)]
95pub enum PasswordStrength {
96 VeryWeak,
97 Weak,
98 Fair,
99 Good,
100 Strong,
101 VeryStrong,
102}
103
104#[derive(Debug, Clone)]
106pub struct PasswordValidation {
107 pub is_valid: bool,
108 pub strength: PasswordStrength,
109 pub issues: Vec<String>,
110 pub suggestions: Vec<String>,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize, Default)]
115pub struct AuditConfig {
116 pub enabled: bool,
117 pub log_success: bool,
118 pub log_failures: bool,
119 pub log_permission_changes: bool,
120 pub log_admin_actions: bool,
121 pub retention_days: u32,
122 pub include_metadata: bool,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize, Default)]
127pub struct LockoutConfig {
128 pub enabled: bool,
129 pub max_failed_attempts: u32,
130 pub lockout_duration_seconds: u64,
131 pub progressive_lockout: bool,
132 pub max_lockout_duration_seconds: u64,
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct SmsConfig {
138 pub provider: String,
139 pub api_key: String,
140 pub from_number: String,
141}
142
143impl Default for SmsConfig {
144 fn default() -> Self {
145 Self {
146 provider: "twilio".to_string(),
147 api_key: String::new(),
148 from_number: String::new(),
149 }
150 }
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct EmailConfig {
156 pub smtp_server: String,
157 pub smtp_port: u16,
158 pub username: String,
159 pub password: String,
160 pub from_email: String,
161 pub use_tls: bool,
162}
163
164impl Default for EmailConfig {
165 fn default() -> Self {
166 Self {
167 smtp_server: "smtp.gmail.com".to_string(),
168 smtp_port: 587,
169 username: String::new(),
170 password: String::new(),
171 from_email: String::new(),
172 use_tls: true,
173 }
174 }
175}
176
177#[derive(Debug, Clone, Default)]
179pub struct SecurityContext {
180 pub ip_address: Option<String>,
181 pub user_agent: Option<String>,
182 pub session_id: Option<String>,
183 pub request_id: Option<String>,
184}
185
186pub struct PasswordValidator {
188 pub min_length: usize,
189 pub require_uppercase: bool,
190 pub require_lowercase: bool,
191 pub require_digits: bool,
192 pub require_special_chars: bool,
193 pub min_special_chars: usize,
194 pub forbidden_patterns: Vec<String>,
195}
196
197impl Default for PasswordValidator {
198 fn default() -> Self {
199 Self {
200 min_length: 8,
201 require_uppercase: true,
202 require_lowercase: true,
203 require_digits: true,
204 require_special_chars: true,
205 min_special_chars: 1,
206 forbidden_patterns: vec![
207 "password".to_string(),
208 "123456".to_string(),
209 "qwerty".to_string(),
210 "admin".to_string(),
211 ],
212 }
213 }
214}
215
216impl PasswordValidator {
217 pub fn validate(&self, password: &str) -> PasswordValidation {
219 let mut is_valid = true;
220 let mut issues = Vec::new();
221 let mut suggestions = Vec::new();
222
223 if password.len() < self.min_length {
225 is_valid = false;
226 issues.push(format!(
227 "Password must be at least {} characters long",
228 self.min_length
229 ));
230 suggestions.push("Use a longer password".to_string());
231 }
232
233 if self.require_uppercase && !password.chars().any(|c| c.is_uppercase()) {
235 is_valid = false;
236 issues.push("Password must contain at least one uppercase letter".to_string());
237 suggestions.push("Add uppercase letters".to_string());
238 }
239
240 if self.require_lowercase && !password.chars().any(|c| c.is_lowercase()) {
241 is_valid = false;
242 issues.push("Password must contain at least one lowercase letter".to_string());
243 suggestions.push("Add lowercase letters".to_string());
244 }
245
246 if self.require_digits && !password.chars().any(|c| c.is_numeric()) {
247 is_valid = false;
248 issues.push("Password must contain at least one digit".to_string());
249 suggestions.push("Add numbers".to_string());
250 }
251
252 if self.require_special_chars {
253 let special_count = password.chars().filter(|&c| !c.is_alphanumeric()).count();
254 if special_count < self.min_special_chars {
255 is_valid = false;
256 issues.push(format!(
257 "Password must contain at least {} special characters",
258 self.min_special_chars
259 ));
260 suggestions.push("Add special characters like !@#$%^&*".to_string());
261 }
262 }
263
264 for pattern in &self.forbidden_patterns {
266 if password.to_lowercase().contains(&pattern.to_lowercase()) {
267 is_valid = false;
268 issues.push(format!("Password contains forbidden pattern: {}", pattern));
269 suggestions.push("Avoid common passwords and patterns".to_string());
270 }
271 }
272
273 let strength = self.assess_strength(password);
275
276 PasswordValidation {
277 is_valid,
278 strength,
279 issues,
280 suggestions,
281 }
282 }
283
284 fn assess_strength(&self, password: &str) -> PasswordStrength {
286 let mut score = 0;
287
288 if password.len() >= 8 {
290 score += 1;
291 }
292 if password.len() >= 12 {
293 score += 1;
294 }
295 if password.len() >= 16 {
296 score += 1;
297 }
298
299 if password.chars().any(|c| c.is_lowercase()) {
301 score += 1;
302 }
303 if password.chars().any(|c| c.is_uppercase()) {
304 score += 1;
305 }
306 if password.chars().any(|c| c.is_numeric()) {
307 score += 1;
308 }
309 if password.chars().any(|c| !c.is_alphanumeric()) {
310 score += 1;
311 }
312
313 if password.len() >= 20 {
315 score += 1;
316 }
317
318 match score {
319 0..=2 => PasswordStrength::VeryWeak,
320 3..=4 => PasswordStrength::Weak,
321 5 => PasswordStrength::Fair,
322 6 => PasswordStrength::Good,
323 7 => PasswordStrength::Strong,
324 _ => PasswordStrength::VeryStrong,
325 }
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332
333 #[test]
334 fn test_password_validation() {
335 let validator = PasswordValidator::default();
336
337 let result = validator.validate("weak");
339 assert!(!result.is_valid);
340 assert_eq!(result.strength, PasswordStrength::VeryWeak);
341 assert!(!result.issues.is_empty());
342
343 let result = validator.validate("Strong@Secure123!");
345 assert!(result.is_valid);
346 assert!(matches!(
347 result.strength,
348 PasswordStrength::Strong | PasswordStrength::VeryStrong
349 ));
350 assert!(result.issues.is_empty());
351 }
352
353 #[test]
354 fn test_forbidden_patterns() {
355 let validator = PasswordValidator::default();
356 let result = validator.validate("Password123!");
357 assert!(!result.is_valid);
358 assert!(
359 result
360 .issues
361 .iter()
362 .any(|issue| issue.contains("forbidden pattern"))
363 );
364 }
365}