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