auth_framework/security/
mod.rs

1use serde::{Deserialize, Serialize};
2
3// Timing protection utilities
4pub mod timing_protection;
5
6// Secure implementations
7pub mod secure_jwt;
8pub mod secure_mfa;
9pub mod secure_session;
10pub mod secure_session_config;
11pub mod secure_utils;
12
13// Security presets for easy configuration
14pub mod presets;
15
16// Re-export presets for convenience
17pub use presets::{
18    SecurityAuditReport, SecurityAuditStatus, SecurityIssue, SecurityPreset, SecuritySeverity,
19};
20
21/// Multi-Factor Authentication configuration
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct MfaConfig {
24    /// TOTP configuration
25    pub totp_config: TotpConfig,
26    /// SMS configuration (optional)
27    pub sms_config: Option<SmsConfig>,
28    /// Email configuration (optional)
29    pub email_config: Option<EmailConfig>,
30    /// Backup codes configuration
31    pub backup_codes_config: BackupCodesConfig,
32    /// Challenge timeout in seconds
33    pub challenge_timeout_seconds: u64,
34    /// Maximum verification attempts
35    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, // 5 minutes
46            max_verification_attempts: 3,
47        }
48    }
49}
50
51/// TOTP (Time-based One-Time Password) configuration
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct TotpConfig {
54    /// Issuer name for TOTP apps
55    pub issuer: String,
56    /// Number of digits in the TOTP code
57    pub digits: u8,
58    /// Time period in seconds
59    pub period: u64,
60    /// Number of time windows to allow (for clock skew)
61    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/// Backup codes configuration
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct BackupCodesConfig {
78    /// Number of backup codes to generate
79    pub count: usize,
80    /// Length of each backup code
81    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/// Password strength assessment result
94#[derive(Debug, Clone, PartialEq)]
95pub enum PasswordStrength {
96    VeryWeak,
97    Weak,
98    Fair,
99    Good,
100    Strong,
101    VeryStrong,
102}
103
104/// Password validation result
105#[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/// Audit logging configuration
114#[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/// Account lockout configuration
126#[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/// SMS configuration
136#[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/// Email configuration
154#[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/// Security context for authentication operations
178#[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
186/// Password validator
187pub 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    /// Validate password strength and return detailed results
218    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        // Check minimum length
224        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        // Check character requirements
234        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        // Check forbidden patterns
265        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        // Assess strength
274        let strength = self.assess_strength(password);
275
276        PasswordValidation {
277            is_valid,
278            strength,
279            issues,
280            suggestions,
281        }
282    }
283
284    /// Assess password strength
285    fn assess_strength(&self, password: &str) -> PasswordStrength {
286        let mut score = 0;
287
288        // Length scoring
289        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        // Character variety scoring
300        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        // Complexity bonus
314        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        // Test weak password
338        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        // Test strong password
344        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}