Skip to main content

auth_framework/security/
presets.rs

1//! Security presets for the Auth Framework
2//!
3//! This module provides pre-configured security levels that automatically
4//! apply appropriate security settings for different environments and use cases.
5//!
6//! # Security Presets
7//!
8//! - **Development**: Convenient settings for development environments
9//! - **Balanced**: Good security with reasonable performance (default)
10//! - **HighSecurity**: Strong security for sensitive applications
11//! - **Paranoid**: Maximum security settings for high-risk environments
12//!
13//! # Usage
14//!
15//! ```rust,no_run
16//! # #[tokio::main]
17//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
18//! use auth_framework::prelude::*;
19//!
20//! // Quick setup with security preset
21//! let auth = AuthFramework::quick_start()
22//!     .jwt_auth_from_env()
23//!     .security_level(SecurityPreset::HighSecurity)
24//!     .build().await?;
25//!
26//! // Or apply to existing configuration
27//! let config = AuthConfig::new()
28//!     .security(SecurityPreset::Paranoid.to_config());
29//! # Ok(())
30//! # }
31//! ```
32//!
33//! # Security Validation
34//!
35//! Each preset includes built-in validation to ensure security requirements
36//! are met for the target environment:
37//!
38//! ```rust,no_run
39//! # #[tokio::main]
40//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
41//! use auth_framework::prelude::*;
42//!
43//! // Validate security configuration
44//! let issues = SecurityPreset::HighSecurity
45//!     .validate_environment()
46//!     .await?;
47//!
48//! for issue in issues {
49//!     println!("âš ī¸  {}: {}", issue.severity, issue.description);
50//!     println!("💡 Fix: {}", issue.suggestion);
51//! }
52//! # Ok(())
53//! # }
54//! ```
55
56use crate::{
57    config::{
58        AuditConfig, AuditStorage, CookieSameSite, JwtAlgorithm, PasswordHashAlgorithm,
59        RateLimitConfig, SecurityConfig,
60    },
61    prelude::{AuthFrameworkResult, hours, minutes},
62};
63use std::time::Duration;
64
65/// Security presets for common configurations
66#[derive(Debug, Clone, PartialEq)]
67pub enum SecurityPreset {
68    /// Development-friendly settings (lower security, more convenient)
69    ///
70    /// **USE ONLY FOR DEVELOPMENT - NOT PRODUCTION SAFE**
71    ///
72    /// - Shorter passwords allowed (6+ chars)
73    /// - Weaker password requirements
74    /// - Less strict cookie settings
75    /// - Disabled CSRF protection for easier testing
76    /// - Longer session timeouts for convenience
77    Development,
78
79    /// Balanced settings for most applications
80    ///
81    /// Good balance of security and usability suitable for most production
82    /// applications that don't handle highly sensitive data.
83    ///
84    /// - Standard password requirements (8+ chars)
85    /// - Secure cookies and CSRF protection
86    /// - Reasonable rate limiting
87    /// - Basic audit logging
88    Balanced,
89
90    /// High security settings for sensitive applications
91    ///
92    /// Strong security settings suitable for applications handling
93    /// sensitive data like financial information, healthcare records,
94    /// or personal data subject to compliance requirements.
95    ///
96    /// - Strict password requirements (12+ chars, complexity)
97    /// - Strong JWT algorithms (RSA-256)
98    /// - Aggressive rate limiting
99    /// - Comprehensive audit logging
100    /// - Short session timeouts
101    HighSecurity,
102
103    /// Maximum security (paranoid mode)
104    ///
105    /// Extremely strict security settings for high-risk environments
106    /// where security is paramount over convenience.
107    ///
108    /// - Very strict password requirements (16+ chars)
109    /// - Strongest cryptographic algorithms
110    /// - Very aggressive rate limiting
111    /// - Extensive audit logging and monitoring
112    /// - Very short session timeouts
113    /// - Constant-time operations
114    Paranoid,
115}
116
117/// Security validation issue
118#[derive(Debug, Clone)]
119pub struct SecurityIssue {
120    /// Severity level of the issue
121    pub severity: SecuritySeverity,
122    /// Component that has the issue
123    pub component: String,
124    /// Description of the security issue
125    pub description: String,
126    /// Suggested fix for the issue
127    pub suggestion: String,
128    /// Whether this issue blocks production deployment
129    pub blocks_production: bool,
130}
131
132/// Security issue severity levels
133#[derive(Debug, Clone, PartialEq, PartialOrd)]
134pub enum SecuritySeverity {
135    /// Information about security configuration
136    Info,
137    /// Security recommendation that should be addressed
138    Warning,
139    /// Security issue that should be fixed
140    Error,
141    /// Critical security issue that must be fixed
142    Critical,
143}
144
145impl SecurityPreset {
146    /// Convert the security preset to a SecurityConfig
147    pub fn to_config(&self) -> SecurityConfig {
148        match self {
149            SecurityPreset::Development => SecurityConfig {
150                min_password_length: 6,
151                require_password_complexity: false,
152                password_hash_algorithm: PasswordHashAlgorithm::Bcrypt, // Faster for development
153                jwt_algorithm: JwtAlgorithm::HS256,
154                secret_key: None, // Must be set externally
155                previous_secret_key: None,
156                secure_cookies: false, // Allow HTTP for local development
157                cookie_same_site: CookieSameSite::Lax,
158                csrf_protection: false,     // Easier API testing
159                session_timeout: hours(24), // Long timeout for development convenience
160            },
161            SecurityPreset::Balanced => SecurityConfig {
162                min_password_length: 8,
163                require_password_complexity: true,
164                password_hash_algorithm: PasswordHashAlgorithm::Argon2,
165                jwt_algorithm: JwtAlgorithm::HS256,
166                secret_key: None,
167                previous_secret_key: None,
168                secure_cookies: true,
169                cookie_same_site: CookieSameSite::Lax,
170                csrf_protection: true,
171                session_timeout: hours(8),
172            },
173            SecurityPreset::HighSecurity => SecurityConfig {
174                min_password_length: 12,
175                require_password_complexity: true,
176                password_hash_algorithm: PasswordHashAlgorithm::Argon2,
177                jwt_algorithm: JwtAlgorithm::RS256, // RSA for better security
178                secret_key: None,
179                previous_secret_key: None,
180                secure_cookies: true,
181                cookie_same_site: CookieSameSite::Strict,
182                csrf_protection: true,
183                session_timeout: hours(2), // Shorter sessions
184            },
185            SecurityPreset::Paranoid => SecurityConfig {
186                min_password_length: 16,
187                require_password_complexity: true,
188                password_hash_algorithm: PasswordHashAlgorithm::Argon2,
189                jwt_algorithm: JwtAlgorithm::RS512, // Strongest RSA
190                secret_key: None,
191                previous_secret_key: None,
192                secure_cookies: true,
193                cookie_same_site: CookieSameSite::Strict,
194                csrf_protection: true,
195                session_timeout: minutes(30), // Very short sessions
196            },
197        }
198    }
199
200    /// Get rate limiting configuration for this security preset
201    pub fn to_rate_limit_config(&self) -> RateLimitConfig {
202        match self {
203            SecurityPreset::Development => RateLimitConfig {
204                enabled: false, // Disabled for easier development
205                max_requests: 1000,
206                window: Duration::from_secs(60),
207                burst: 100,
208            },
209            SecurityPreset::Balanced => RateLimitConfig {
210                enabled: true,
211                max_requests: 100,
212                window: Duration::from_secs(60),
213                burst: 20,
214            },
215            SecurityPreset::HighSecurity => RateLimitConfig {
216                enabled: true,
217                max_requests: 60, // 1 per second average
218                window: Duration::from_secs(60),
219                burst: 10,
220            },
221            SecurityPreset::Paranoid => RateLimitConfig {
222                enabled: true,
223                max_requests: 30, // 0.5 per second average
224                window: Duration::from_secs(60),
225                burst: 5,
226            },
227        }
228    }
229
230    /// Get audit configuration for this security preset
231    pub fn to_audit_config(&self) -> AuditConfig {
232        match self {
233            SecurityPreset::Development => AuditConfig {
234                enabled: false, // Disabled for cleaner development logs
235                log_success: false,
236                log_failures: true, // Still log failures for debugging
237                log_permissions: false,
238                log_tokens: false,
239                storage: AuditStorage::Tracing,
240            },
241            SecurityPreset::Balanced => AuditConfig {
242                enabled: true,
243                log_success: false, // Don't log every success to reduce noise
244                log_failures: true,
245                log_permissions: true,
246                log_tokens: false, // Tokens can be sensitive
247                storage: AuditStorage::Tracing,
248            },
249            SecurityPreset::HighSecurity => AuditConfig {
250                enabled: true,
251                log_success: true,
252                log_failures: true,
253                log_permissions: true,
254                log_tokens: false,
255                storage: AuditStorage::Tracing, // Should be database in real deployment
256            },
257            SecurityPreset::Paranoid => AuditConfig {
258                enabled: true,
259                log_success: true,
260                log_failures: true,
261                log_permissions: true,
262                log_tokens: true,               // Log everything in paranoid mode
263                storage: AuditStorage::Tracing, // Should be secure external service
264            },
265        }
266    }
267
268    /// Validate the current environment against this security preset
269    pub async fn validate_environment(&self) -> AuthFrameworkResult<Vec<SecurityIssue>> {
270        let mut issues = Vec::new();
271
272        // Check environment type
273        let is_production = self.is_production_environment();
274        let is_development = self.is_development_environment();
275
276        // Validate preset appropriateness for environment
277        match (self, is_production, is_development) {
278            (SecurityPreset::Development, true, false) => {
279                issues.push(SecurityIssue {
280                    severity: SecuritySeverity::Critical,
281                    component: "Security Preset".to_string(),
282                    description: "Development security preset used in production environment".to_string(),
283                    suggestion: "Use SecurityPreset::HighSecurity or SecurityPreset::Paranoid for production".to_string(),
284                    blocks_production: true,
285                });
286            }
287            (SecurityPreset::Balanced, true, false) => {
288                issues.push(SecurityIssue {
289                    severity: SecuritySeverity::Warning,
290                    component: "Security Preset".to_string(),
291                    description:
292                        "Balanced security preset in production - consider higher security"
293                            .to_string(),
294                    suggestion: "Consider SecurityPreset::HighSecurity for better protection"
295                        .to_string(),
296                    blocks_production: false,
297                });
298            }
299            _ => {} // Other combinations are acceptable
300        }
301
302        // Check JWT secret
303        self.validate_jwt_secret(&mut issues);
304
305        // Check HTTPS in production
306        if is_production && self.requires_secure_cookies() {
307            self.validate_https_requirement(&mut issues);
308        }
309
310        // Check database configuration
311        self.validate_storage_security(&mut issues);
312
313        // Check environment variables
314        self.validate_environment_variables(&mut issues);
315
316        Ok(issues)
317    }
318
319    /// Perform a security audit of the current configuration
320    pub async fn security_audit(&self) -> AuthFrameworkResult<SecurityAuditReport> {
321        let issues = self.validate_environment().await?;
322
323        let critical_count = issues
324            .iter()
325            .filter(|i| i.severity == SecuritySeverity::Critical)
326            .count();
327        let error_count = issues
328            .iter()
329            .filter(|i| i.severity == SecuritySeverity::Error)
330            .count();
331        let warning_count = issues
332            .iter()
333            .filter(|i| i.severity == SecuritySeverity::Warning)
334            .count();
335
336        let overall_status = if critical_count > 0 {
337            SecurityAuditStatus::Critical
338        } else if error_count > 0 {
339            SecurityAuditStatus::Failed
340        } else if warning_count > 0 {
341            SecurityAuditStatus::Warning
342        } else {
343            SecurityAuditStatus::Passed
344        };
345
346        Ok(SecurityAuditReport {
347            preset: self.clone(),
348            status: overall_status,
349            issues,
350            critical_count,
351            error_count,
352            warning_count,
353            recommendations: self.get_security_recommendations(),
354        })
355    }
356
357    /// Get security recommendations for this preset
358    pub fn get_security_recommendations(&self) -> Vec<String> {
359        let mut recommendations = Vec::new();
360
361        match self {
362            SecurityPreset::Development => {
363                recommendations.push(
364                    "âš ī¸  Development preset detected - ensure this is not used in production"
365                        .to_string(),
366                );
367                recommendations
368                    .push("🔐 Set JWT_SECRET environment variable with a secure value".to_string());
369                recommendations
370                    .push("📝 Enable audit logging when moving to production".to_string());
371            }
372            SecurityPreset::Balanced => {
373                recommendations.push(
374                    "🔒 Consider upgrading to HighSecurity for sensitive applications".to_string(),
375                );
376                recommendations
377                    .push("📊 Monitor authentication patterns for suspicious activity".to_string());
378                recommendations.push("🔄 Regularly rotate JWT secrets and API keys".to_string());
379            }
380            SecurityPreset::HighSecurity => {
381                recommendations
382                    .push("✅ Good security configuration for production use".to_string());
383                recommendations
384                    .push("🔐 Ensure RSA keys are properly managed and rotated".to_string());
385                recommendations.push("📈 Monitor failed authentication attempts".to_string());
386                recommendations.push(
387                    "đŸ›Ąī¸  Consider multi-factor authentication for admin accounts".to_string(),
388                );
389            }
390            SecurityPreset::Paranoid => {
391                recommendations
392                    .push("đŸ›Ąī¸  Maximum security enabled - monitor performance impact".to_string());
393                recommendations.push(
394                    "⚡ Consider connection pooling to handle strict rate limits".to_string(),
395                );
396                recommendations.push("🔍 Implement comprehensive security monitoring".to_string());
397                recommendations
398                    .push("🚨 Set up alerting for all authentication failures".to_string());
399            }
400        }
401
402        recommendations
403    }
404
405    // Helper methods for validation
406
407    fn is_production_environment(&self) -> bool {
408        std::env::var("ENVIRONMENT").as_deref() == Ok("production")
409            || std::env::var("ENV").as_deref() == Ok("production")
410            || std::env::var("NODE_ENV").as_deref() == Ok("production")
411            || std::env::var("RUST_ENV").as_deref() == Ok("production")
412            || std::env::var("KUBERNETES_SERVICE_HOST").is_ok()
413    }
414
415    fn is_development_environment(&self) -> bool {
416        std::env::var("ENVIRONMENT").as_deref() == Ok("development")
417            || std::env::var("ENV").as_deref() == Ok("development")
418            || std::env::var("NODE_ENV").as_deref() == Ok("development")
419            || std::env::var("RUST_ENV").as_deref() == Ok("development")
420            || cfg!(debug_assertions)
421    }
422
423    fn requires_secure_cookies(&self) -> bool {
424        matches!(
425            self,
426            SecurityPreset::Balanced | SecurityPreset::HighSecurity | SecurityPreset::Paranoid
427        )
428    }
429
430    fn validate_jwt_secret(&self, issues: &mut Vec<SecurityIssue>) {
431        if let Ok(secret) = std::env::var("JWT_SECRET") {
432            let min_length = match self {
433                SecurityPreset::Development => 16,
434                SecurityPreset::Balanced => 32,
435                SecurityPreset::HighSecurity => 64,
436                SecurityPreset::Paranoid => 128,
437            };
438
439            if secret.len() < min_length {
440                issues.push(SecurityIssue {
441                    severity: if matches!(self, SecurityPreset::Development) {
442                        SecuritySeverity::Warning
443                    } else {
444                        SecuritySeverity::Error
445                    },
446                    component: "JWT Secret".to_string(),
447                    description: format!(
448                        "JWT secret too short ({} chars, need {}+)",
449                        secret.len(),
450                        min_length
451                    ),
452                    suggestion: format!(
453                        "Generate a longer secret: `openssl rand -base64 {}`",
454                        min_length * 3 / 4
455                    ),
456                    blocks_production: !matches!(self, SecurityPreset::Development),
457                });
458            }
459
460            // Check for weak patterns
461            if secret.to_lowercase().contains("secret")
462                || secret.to_lowercase().contains("password")
463                || secret.contains("123")
464            {
465                issues.push(SecurityIssue {
466                    severity: SecuritySeverity::Error,
467                    component: "JWT Secret".to_string(),
468                    description: "JWT secret contains weak patterns or common words".to_string(),
469                    suggestion:
470                        "Use a cryptographically secure random string: `openssl rand -base64 64`"
471                            .to_string(),
472                    blocks_production: true,
473                });
474            }
475        } else {
476            issues.push(SecurityIssue {
477                severity: SecuritySeverity::Critical,
478                component: "JWT Secret".to_string(),
479                description: "JWT_SECRET environment variable not set".to_string(),
480                suggestion: "Set JWT_SECRET environment variable with a secure random value"
481                    .to_string(),
482                blocks_production: true,
483            });
484        }
485    }
486
487    fn validate_https_requirement(&self, issues: &mut Vec<SecurityIssue>) {
488        // In a real implementation, this would check if HTTPS is properly configured
489        // For now, we'll check for common HTTPS indicators
490        let has_tls_cert =
491            std::env::var("TLS_CERT_PATH").is_ok() || std::env::var("SSL_CERT_PATH").is_ok();
492        let behind_proxy = std::env::var("HTTPS").as_deref() == Ok("on")
493            || std::env::var("HTTP_X_FORWARDED_PROTO").as_deref() == Ok("https");
494
495        if !has_tls_cert && !behind_proxy {
496            issues.push(SecurityIssue {
497                severity: SecuritySeverity::Warning,
498                component: "HTTPS".to_string(),
499                description: "HTTPS configuration not detected".to_string(),
500                suggestion: "Ensure HTTPS is properly configured for secure cookie transmission"
501                    .to_string(),
502                blocks_production: false,
503            });
504        }
505    }
506
507    fn validate_storage_security(&self, issues: &mut Vec<SecurityIssue>) {
508        // Check for database connection security
509        if let Ok(db_url) = std::env::var("DATABASE_URL")
510            && db_url.starts_with("postgresql://")
511            && !db_url.contains("sslmode=require")
512        {
513            issues.push(SecurityIssue {
514                severity: SecuritySeverity::Warning,
515                component: "Database".to_string(),
516                description: "Database connection may not be using SSL".to_string(),
517                suggestion: "Add sslmode=require to DATABASE_URL for encrypted connections"
518                    .to_string(),
519                blocks_production: false,
520            });
521        }
522
523        if let Ok(redis_url) = std::env::var("REDIS_URL")
524            && !redis_url.starts_with("rediss://")
525            && !redis_url.contains("tls")
526        {
527            issues.push(SecurityIssue {
528                severity: SecuritySeverity::Info,
529                component: "Redis".to_string(),
530                description: "Redis connection may not be using TLS".to_string(),
531                suggestion: "Consider using rediss:// URL or enabling TLS for Redis connections"
532                    .to_string(),
533                blocks_production: false,
534            });
535        }
536    }
537
538    fn validate_environment_variables(&self, issues: &mut Vec<SecurityIssue>) {
539        let sensitive_vars = [
540            "JWT_SECRET",
541            "DATABASE_URL",
542            "REDIS_URL",
543            "OAUTH_CLIENT_SECRET",
544        ];
545
546        for var in &sensitive_vars {
547            if let Ok(value) = std::env::var(var)
548                && value.len() < 20
549            {
550                issues.push(SecurityIssue {
551                    severity: SecuritySeverity::Warning,
552                    component: "Environment Variables".to_string(),
553                    description: format!("{} appears to be too short", var),
554                    suggestion: format!(
555                        "Ensure {} contains a sufficiently long, secure value",
556                        var
557                    ),
558                    blocks_production: false,
559                });
560            }
561        }
562    }
563}
564
565/// Security audit report
566#[derive(Debug, Clone)]
567pub struct SecurityAuditReport {
568    pub preset: SecurityPreset,
569    pub status: SecurityAuditStatus,
570    pub issues: Vec<SecurityIssue>,
571    pub critical_count: usize,
572    pub error_count: usize,
573    pub warning_count: usize,
574    pub recommendations: Vec<String>,
575}
576
577/// Overall security audit status
578#[derive(Debug, Clone, PartialEq)]
579pub enum SecurityAuditStatus {
580    /// All security checks passed
581    Passed,
582    /// Non-critical warnings found
583    Warning,
584    /// Security errors found that should be addressed
585    Failed,
586    /// Critical security issues that block production deployment
587    Critical,
588}
589
590impl SecurityAuditReport {
591    /// Print a formatted security report to stdout
592    pub fn print_report(&self) {
593        println!("🔒 Security Audit Report");
594        println!("========================");
595        println!("Preset: {:?}", self.preset);
596        println!("Status: {}", self.status_emoji());
597        println!();
598
599        if self.issues.is_empty() {
600            println!("✅ No security issues found!");
601        } else {
602            println!("📊 Issues Summary:");
603            println!("   Critical: {}", self.critical_count);
604            println!("   Errors: {}", self.error_count);
605            println!("   Warnings: {}", self.warning_count);
606            println!();
607
608            for issue in &self.issues {
609                println!(
610                    "{} {}: {}",
611                    issue.severity.emoji(),
612                    issue.component,
613                    issue.description
614                );
615                println!("   💡 {}", issue.suggestion);
616                println!();
617            }
618        }
619
620        if !self.recommendations.is_empty() {
621            println!("📋 Recommendations:");
622            for rec in &self.recommendations {
623                println!("   {}", rec);
624            }
625            println!();
626        }
627
628        println!("{}", self.get_summary_message());
629    }
630
631    fn status_emoji(&self) -> &str {
632        match self.status {
633            SecurityAuditStatus::Passed => "✅ Passed",
634            SecurityAuditStatus::Warning => "âš ī¸  Warning",
635            SecurityAuditStatus::Failed => "❌ Failed",
636            SecurityAuditStatus::Critical => "🚨 Critical",
637        }
638    }
639
640    fn get_summary_message(&self) -> String {
641        match self.status {
642            SecurityAuditStatus::Passed => "🎉 Security audit passed! Your configuration meets security requirements.".to_string(),
643            SecurityAuditStatus::Warning => "âš ī¸  Security audit completed with warnings. Consider addressing the issues above.".to_string(),
644            SecurityAuditStatus::Failed => "❌ Security audit failed. Please address the errors before deploying to production.".to_string(),
645            SecurityAuditStatus::Critical => "🚨 Critical security issues found! Do not deploy to production until these are resolved.".to_string(),
646        }
647    }
648
649    /// Check if the configuration is safe for production deployment
650    pub fn is_production_ready(&self) -> bool {
651        matches!(
652            self.status,
653            SecurityAuditStatus::Passed | SecurityAuditStatus::Warning
654        )
655    }
656
657    /// Get all blocking issues that prevent production deployment
658    pub fn get_blocking_issues(&self) -> Vec<&SecurityIssue> {
659        self.issues
660            .iter()
661            .filter(|issue| issue.blocks_production)
662            .collect()
663    }
664}
665
666impl SecuritySeverity {
667    fn emoji(&self) -> &str {
668        match self {
669            SecuritySeverity::Info => "â„šī¸ ",
670            SecuritySeverity::Warning => "âš ī¸ ",
671            SecuritySeverity::Error => "❌",
672            SecuritySeverity::Critical => "🚨",
673        }
674    }
675}
676
677impl std::fmt::Display for SecuritySeverity {
678    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
679        match self {
680            SecuritySeverity::Info => write!(f, "INFO"),
681            SecuritySeverity::Warning => write!(f, "WARNING"),
682            SecuritySeverity::Error => write!(f, "ERROR"),
683            SecuritySeverity::Critical => write!(f, "CRITICAL"),
684        }
685    }
686}
687
688#[cfg(test)]
689mod tests {
690    use super::*;
691
692    #[test]
693    fn test_development_preset_config() {
694        let config = SecurityPreset::Development.to_config();
695        assert_eq!(config.min_password_length, 6);
696        assert!(!config.require_password_complexity);
697        assert!(!config.secure_cookies);
698        assert!(!config.csrf_protection);
699    }
700
701    #[test]
702    fn test_balanced_preset_config() {
703        let config = SecurityPreset::Balanced.to_config();
704        assert_eq!(config.min_password_length, 8);
705        assert!(config.require_password_complexity);
706        assert!(config.secure_cookies);
707        assert!(config.csrf_protection);
708    }
709
710    #[test]
711    fn test_high_security_preset_config() {
712        let config = SecurityPreset::HighSecurity.to_config();
713        assert_eq!(config.min_password_length, 12);
714        assert!(config.secure_cookies);
715        assert!(config.csrf_protection);
716        assert!(matches!(config.cookie_same_site, CookieSameSite::Strict));
717    }
718
719    #[test]
720    fn test_paranoid_preset_config() {
721        let config = SecurityPreset::Paranoid.to_config();
722        assert_eq!(config.min_password_length, 16);
723        assert!(config.csrf_protection);
724        assert_eq!(config.session_timeout, minutes(30));
725    }
726
727    #[test]
728    fn test_presets_have_increasing_password_length() {
729        let dev = SecurityPreset::Development.to_config().min_password_length;
730        let bal = SecurityPreset::Balanced.to_config().min_password_length;
731        let high = SecurityPreset::HighSecurity.to_config().min_password_length;
732        let paranoid = SecurityPreset::Paranoid.to_config().min_password_length;
733        assert!(dev < bal);
734        assert!(bal < high);
735        assert!(high < paranoid);
736    }
737
738    #[test]
739    fn test_presets_have_decreasing_session_timeout() {
740        let dev = SecurityPreset::Development.to_config().session_timeout;
741        let bal = SecurityPreset::Balanced.to_config().session_timeout;
742        let high = SecurityPreset::HighSecurity.to_config().session_timeout;
743        let paranoid = SecurityPreset::Paranoid.to_config().session_timeout;
744        assert!(dev > bal);
745        assert!(bal > high);
746        assert!(high > paranoid);
747    }
748
749    #[test]
750    fn test_rate_limit_configs_are_valid() {
751        for preset in [
752            SecurityPreset::Development,
753            SecurityPreset::Balanced,
754            SecurityPreset::HighSecurity,
755            SecurityPreset::Paranoid,
756        ] {
757            let rl = preset.to_rate_limit_config();
758            assert!(rl.max_requests > 0);
759            assert!(!rl.window.is_zero());
760        }
761    }
762
763    #[test]
764    fn test_audit_configs_are_valid() {
765        for preset in [
766            SecurityPreset::Development,
767            SecurityPreset::Balanced,
768            SecurityPreset::HighSecurity,
769            SecurityPreset::Paranoid,
770        ] {
771            let ac = preset.to_audit_config();
772            // All presets produce a valid config; HighSecurity+ enable logging
773            if matches!(preset, SecurityPreset::HighSecurity | SecurityPreset::Paranoid) {
774                assert!(ac.enabled);
775            }
776        }
777    }
778
779    #[test]
780    fn test_security_recommendations_non_empty() {
781        for preset in [
782            SecurityPreset::Development,
783            SecurityPreset::Balanced,
784            SecurityPreset::HighSecurity,
785            SecurityPreset::Paranoid,
786        ] {
787            let recs = preset.get_security_recommendations();
788            assert!(!recs.is_empty());
789        }
790    }
791
792    #[test]
793    fn test_audit_report_production_ready() {
794        let report = SecurityAuditReport {
795            preset: SecurityPreset::Balanced,
796            status: SecurityAuditStatus::Passed,
797            issues: vec![],
798            critical_count: 0,
799            error_count: 0,
800            warning_count: 0,
801            recommendations: vec![],
802        };
803        assert!(report.is_production_ready());
804    }
805
806    #[test]
807    fn test_audit_report_not_production_ready_on_critical() {
808        let report = SecurityAuditReport {
809            preset: SecurityPreset::HighSecurity,
810            status: SecurityAuditStatus::Critical,
811            issues: vec![SecurityIssue {
812                severity: SecuritySeverity::Critical,
813                component: "test".into(),
814                description: "fail".into(),
815                suggestion: "fix".into(),
816                blocks_production: true,
817            }],
818            critical_count: 1,
819            error_count: 0,
820            warning_count: 0,
821            recommendations: vec![],
822        };
823        assert!(!report.is_production_ready());
824        assert_eq!(report.get_blocking_issues().len(), 1);
825    }
826
827    #[test]
828    fn test_severity_display() {
829        assert_eq!(format!("{}", SecuritySeverity::Critical), "CRITICAL");
830        assert_eq!(format!("{}", SecuritySeverity::Warning), "WARNING");
831    }
832}