1use 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#[derive(Debug, Clone, PartialEq)]
59pub enum SecurityPreset {
60 Development,
70
71 Balanced,
81
82 HighSecurity,
94
95 Paranoid,
107}
108
109#[derive(Debug, Clone)]
111pub struct SecurityIssue {
112 pub severity: SecuritySeverity,
114 pub component: String,
116 pub description: String,
118 pub suggestion: String,
120 pub blocks_production: bool,
122}
123
124#[derive(Debug, Clone, PartialEq, PartialOrd)]
126pub enum SecuritySeverity {
127 Info,
129 Warning,
131 Error,
133 Critical,
135}
136
137impl SecurityPreset {
138 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, jwt_algorithm: JwtAlgorithm::HS256,
146 secret_key: None, secure_cookies: false, cookie_same_site: CookieSameSite::Lax,
149 csrf_protection: false, session_timeout: hours(24), },
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, secret_key: None,
169 secure_cookies: true,
170 cookie_same_site: CookieSameSite::Strict,
171 csrf_protection: true,
172 session_timeout: hours(2), },
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, secret_key: None,
180 secure_cookies: true,
181 cookie_same_site: CookieSameSite::Strict,
182 csrf_protection: true,
183 session_timeout: minutes(30), },
185 }
186 }
187
188 pub fn to_rate_limit_config(&self) -> RateLimitConfig {
190 match self {
191 SecurityPreset::Development => RateLimitConfig {
192 enabled: false, 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, window: Duration::from_secs(60),
207 burst: 10,
208 },
209 SecurityPreset::Paranoid => RateLimitConfig {
210 enabled: true,
211 max_requests: 30, window: Duration::from_secs(60),
213 burst: 5,
214 },
215 }
216 }
217
218 pub fn to_audit_config(&self) -> AuditConfig {
220 match self {
221 SecurityPreset::Development => AuditConfig {
222 enabled: false, log_success: false,
224 log_failures: true, log_permissions: false,
226 log_tokens: false,
227 storage: AuditStorage::Tracing,
228 },
229 SecurityPreset::Balanced => AuditConfig {
230 enabled: true,
231 log_success: false, log_failures: true,
233 log_permissions: true,
234 log_tokens: false, 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, },
245 SecurityPreset::Paranoid => AuditConfig {
246 enabled: true,
247 log_success: true,
248 log_failures: true,
249 log_permissions: true,
250 log_tokens: true, storage: AuditStorage::Tracing, },
253 }
254 }
255
256 pub async fn validate_environment(&self) -> AuthFrameworkResult<Vec<SecurityIssue>> {
258 let mut issues = Vec::new();
259
260 let is_production = self.is_production_environment();
262 let is_development = self.is_development_environment();
263
264 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 _ => {} }
289
290 self.validate_jwt_secret(&mut issues);
292
293 if is_production && self.requires_secure_cookies() {
295 self.validate_https_requirement(&mut issues);
296 }
297
298 self.validate_storage_security(&mut issues);
300
301 self.validate_environment_variables(&mut issues);
303
304 Ok(issues)
305 }
306
307 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 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 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 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 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 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#[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#[derive(Debug, Clone, PartialEq)]
567pub enum SecurityAuditStatus {
568 Passed,
570 Warning,
572 Failed,
574 Critical,
576}
577
578impl SecurityAuditReport {
579 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 pub fn is_production_ready(&self) -> bool {
639 matches!(
640 self.status,
641 SecurityAuditStatus::Passed | SecurityAuditStatus::Warning
642 )
643 }
644
645 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}