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