use crate::{
config::{
AuditConfig, AuditStorage, CookieSameSite, JwtAlgorithm, PasswordHashAlgorithm,
RateLimitConfig, SecurityConfig,
},
prelude::{AuthFrameworkResult, hours, minutes},
};
use std::time::Duration;
#[derive(Debug, Clone, PartialEq)]
pub enum SecurityPreset {
Development,
Balanced,
HighSecurity,
Paranoid,
}
#[derive(Debug, Clone)]
pub struct SecurityIssue {
pub severity: SecuritySeverity,
pub component: String,
pub description: String,
pub suggestion: String,
pub blocks_production: bool,
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum SecuritySeverity {
Info,
Warning,
Error,
Critical,
}
impl SecurityPreset {
pub fn to_config(&self) -> SecurityConfig {
match self {
SecurityPreset::Development => SecurityConfig {
min_password_length: 6,
require_password_complexity: false,
password_hash_algorithm: PasswordHashAlgorithm::Bcrypt, jwt_algorithm: JwtAlgorithm::HS256,
secret_key: None, secure_cookies: false, cookie_same_site: CookieSameSite::Lax,
csrf_protection: false, session_timeout: hours(24), },
SecurityPreset::Balanced => SecurityConfig {
min_password_length: 8,
require_password_complexity: true,
password_hash_algorithm: PasswordHashAlgorithm::Argon2,
jwt_algorithm: JwtAlgorithm::HS256,
secret_key: None,
secure_cookies: true,
cookie_same_site: CookieSameSite::Lax,
csrf_protection: true,
session_timeout: hours(8),
},
SecurityPreset::HighSecurity => SecurityConfig {
min_password_length: 12,
require_password_complexity: true,
password_hash_algorithm: PasswordHashAlgorithm::Argon2,
jwt_algorithm: JwtAlgorithm::RS256, secret_key: None,
secure_cookies: true,
cookie_same_site: CookieSameSite::Strict,
csrf_protection: true,
session_timeout: hours(2), },
SecurityPreset::Paranoid => SecurityConfig {
min_password_length: 16,
require_password_complexity: true,
password_hash_algorithm: PasswordHashAlgorithm::Argon2,
jwt_algorithm: JwtAlgorithm::RS512, secret_key: None,
secure_cookies: true,
cookie_same_site: CookieSameSite::Strict,
csrf_protection: true,
session_timeout: minutes(30), },
}
}
pub fn to_rate_limit_config(&self) -> RateLimitConfig {
match self {
SecurityPreset::Development => RateLimitConfig {
enabled: false, max_requests: 1000,
window: Duration::from_secs(60),
burst: 100,
},
SecurityPreset::Balanced => RateLimitConfig {
enabled: true,
max_requests: 100,
window: Duration::from_secs(60),
burst: 20,
},
SecurityPreset::HighSecurity => RateLimitConfig {
enabled: true,
max_requests: 60, window: Duration::from_secs(60),
burst: 10,
},
SecurityPreset::Paranoid => RateLimitConfig {
enabled: true,
max_requests: 30, window: Duration::from_secs(60),
burst: 5,
},
}
}
pub fn to_audit_config(&self) -> AuditConfig {
match self {
SecurityPreset::Development => AuditConfig {
enabled: false, log_success: false,
log_failures: true, log_permissions: false,
log_tokens: false,
storage: AuditStorage::Tracing,
},
SecurityPreset::Balanced => AuditConfig {
enabled: true,
log_success: false, log_failures: true,
log_permissions: true,
log_tokens: false, storage: AuditStorage::Tracing,
},
SecurityPreset::HighSecurity => AuditConfig {
enabled: true,
log_success: true,
log_failures: true,
log_permissions: true,
log_tokens: false,
storage: AuditStorage::Tracing, },
SecurityPreset::Paranoid => AuditConfig {
enabled: true,
log_success: true,
log_failures: true,
log_permissions: true,
log_tokens: true, storage: AuditStorage::Tracing, },
}
}
pub async fn validate_environment(&self) -> AuthFrameworkResult<Vec<SecurityIssue>> {
let mut issues = Vec::new();
let is_production = self.is_production_environment();
let is_development = self.is_development_environment();
match (self, is_production, is_development) {
(SecurityPreset::Development, true, false) => {
issues.push(SecurityIssue {
severity: SecuritySeverity::Critical,
component: "Security Preset".to_string(),
description: "Development security preset used in production environment".to_string(),
suggestion: "Use SecurityPreset::HighSecurity or SecurityPreset::Paranoid for production".to_string(),
blocks_production: true,
});
}
(SecurityPreset::Balanced, true, false) => {
issues.push(SecurityIssue {
severity: SecuritySeverity::Warning,
component: "Security Preset".to_string(),
description:
"Balanced security preset in production - consider higher security"
.to_string(),
suggestion: "Consider SecurityPreset::HighSecurity for better protection"
.to_string(),
blocks_production: false,
});
}
_ => {} }
self.validate_jwt_secret(&mut issues);
if is_production && self.requires_secure_cookies() {
self.validate_https_requirement(&mut issues);
}
self.validate_storage_security(&mut issues);
self.validate_environment_variables(&mut issues);
Ok(issues)
}
pub async fn security_audit(&self) -> AuthFrameworkResult<SecurityAuditReport> {
let issues = self.validate_environment().await?;
let critical_count = issues
.iter()
.filter(|i| i.severity == SecuritySeverity::Critical)
.count();
let error_count = issues
.iter()
.filter(|i| i.severity == SecuritySeverity::Error)
.count();
let warning_count = issues
.iter()
.filter(|i| i.severity == SecuritySeverity::Warning)
.count();
let overall_status = if critical_count > 0 {
SecurityAuditStatus::Critical
} else if error_count > 0 {
SecurityAuditStatus::Failed
} else if warning_count > 0 {
SecurityAuditStatus::Warning
} else {
SecurityAuditStatus::Passed
};
Ok(SecurityAuditReport {
preset: self.clone(),
status: overall_status,
issues,
critical_count,
error_count,
warning_count,
recommendations: self.get_security_recommendations(),
})
}
pub fn get_security_recommendations(&self) -> Vec<String> {
let mut recommendations = Vec::new();
match self {
SecurityPreset::Development => {
recommendations.push(
"â ī¸ Development preset detected - ensure this is not used in production"
.to_string(),
);
recommendations
.push("đ Set JWT_SECRET environment variable with a secure value".to_string());
recommendations
.push("đ Enable audit logging when moving to production".to_string());
}
SecurityPreset::Balanced => {
recommendations.push(
"đ Consider upgrading to HighSecurity for sensitive applications".to_string(),
);
recommendations
.push("đ Monitor authentication patterns for suspicious activity".to_string());
recommendations.push("đ Regularly rotate JWT secrets and API keys".to_string());
}
SecurityPreset::HighSecurity => {
recommendations
.push("â
Good security configuration for production use".to_string());
recommendations
.push("đ Ensure RSA keys are properly managed and rotated".to_string());
recommendations.push("đ Monitor failed authentication attempts".to_string());
recommendations.push(
"đĄī¸ Consider multi-factor authentication for admin accounts".to_string(),
);
}
SecurityPreset::Paranoid => {
recommendations
.push("đĄī¸ Maximum security enabled - monitor performance impact".to_string());
recommendations.push(
"⥠Consider connection pooling to handle strict rate limits".to_string(),
);
recommendations.push("đ Implement comprehensive security monitoring".to_string());
recommendations
.push("đ¨ Set up alerting for all authentication failures".to_string());
}
}
recommendations
}
fn is_production_environment(&self) -> bool {
std::env::var("ENVIRONMENT").as_deref() == Ok("production")
|| std::env::var("ENV").as_deref() == Ok("production")
|| std::env::var("NODE_ENV").as_deref() == Ok("production")
|| std::env::var("RUST_ENV").as_deref() == Ok("production")
|| std::env::var("KUBERNETES_SERVICE_HOST").is_ok()
}
fn is_development_environment(&self) -> bool {
std::env::var("ENVIRONMENT").as_deref() == Ok("development")
|| std::env::var("ENV").as_deref() == Ok("development")
|| std::env::var("NODE_ENV").as_deref() == Ok("development")
|| std::env::var("RUST_ENV").as_deref() == Ok("development")
|| cfg!(debug_assertions)
}
fn requires_secure_cookies(&self) -> bool {
matches!(
self,
SecurityPreset::Balanced | SecurityPreset::HighSecurity | SecurityPreset::Paranoid
)
}
fn validate_jwt_secret(&self, issues: &mut Vec<SecurityIssue>) {
if let Ok(secret) = std::env::var("JWT_SECRET") {
let min_length = match self {
SecurityPreset::Development => 16,
SecurityPreset::Balanced => 32,
SecurityPreset::HighSecurity => 64,
SecurityPreset::Paranoid => 128,
};
if secret.len() < min_length {
issues.push(SecurityIssue {
severity: if matches!(self, SecurityPreset::Development) {
SecuritySeverity::Warning
} else {
SecuritySeverity::Error
},
component: "JWT Secret".to_string(),
description: format!(
"JWT secret too short ({} chars, need {}+)",
secret.len(),
min_length
),
suggestion: format!(
"Generate a longer secret: `openssl rand -base64 {}`",
min_length * 3 / 4
),
blocks_production: !matches!(self, SecurityPreset::Development),
});
}
if secret.to_lowercase().contains("secret")
|| secret.to_lowercase().contains("password")
|| secret.contains("123")
{
issues.push(SecurityIssue {
severity: SecuritySeverity::Error,
component: "JWT Secret".to_string(),
description: "JWT secret contains weak patterns or common words".to_string(),
suggestion:
"Use a cryptographically secure random string: `openssl rand -base64 64`"
.to_string(),
blocks_production: true,
});
}
} else {
issues.push(SecurityIssue {
severity: SecuritySeverity::Critical,
component: "JWT Secret".to_string(),
description: "JWT_SECRET environment variable not set".to_string(),
suggestion: "Set JWT_SECRET environment variable with a secure random value"
.to_string(),
blocks_production: true,
});
}
}
fn validate_https_requirement(&self, issues: &mut Vec<SecurityIssue>) {
let has_tls_cert =
std::env::var("TLS_CERT_PATH").is_ok() || std::env::var("SSL_CERT_PATH").is_ok();
let behind_proxy = std::env::var("HTTPS").as_deref() == Ok("on")
|| std::env::var("HTTP_X_FORWARDED_PROTO").as_deref() == Ok("https");
if !has_tls_cert && !behind_proxy {
issues.push(SecurityIssue {
severity: SecuritySeverity::Warning,
component: "HTTPS".to_string(),
description: "HTTPS configuration not detected".to_string(),
suggestion: "Ensure HTTPS is properly configured for secure cookie transmission"
.to_string(),
blocks_production: false,
});
}
}
fn validate_storage_security(&self, issues: &mut Vec<SecurityIssue>) {
if let Ok(db_url) = std::env::var("DATABASE_URL")
&& db_url.starts_with("postgresql://")
&& !db_url.contains("sslmode=require")
{
issues.push(SecurityIssue {
severity: SecuritySeverity::Warning,
component: "Database".to_string(),
description: "Database connection may not be using SSL".to_string(),
suggestion: "Add sslmode=require to DATABASE_URL for encrypted connections"
.to_string(),
blocks_production: false,
});
}
if let Ok(redis_url) = std::env::var("REDIS_URL")
&& !redis_url.starts_with("rediss://")
&& !redis_url.contains("tls")
{
issues.push(SecurityIssue {
severity: SecuritySeverity::Info,
component: "Redis".to_string(),
description: "Redis connection may not be using TLS".to_string(),
suggestion: "Consider using rediss:// URL or enabling TLS for Redis connections"
.to_string(),
blocks_production: false,
});
}
}
fn validate_environment_variables(&self, issues: &mut Vec<SecurityIssue>) {
let sensitive_vars = [
"JWT_SECRET",
"DATABASE_URL",
"REDIS_URL",
"OAUTH_CLIENT_SECRET",
];
for var in &sensitive_vars {
if let Ok(value) = std::env::var(var)
&& value.len() < 20
{
issues.push(SecurityIssue {
severity: SecuritySeverity::Warning,
component: "Environment Variables".to_string(),
description: format!("{} appears to be too short", var),
suggestion: format!(
"Ensure {} contains a sufficiently long, secure value",
var
),
blocks_production: false,
});
}
}
}
}
#[derive(Debug, Clone)]
pub struct SecurityAuditReport {
pub preset: SecurityPreset,
pub status: SecurityAuditStatus,
pub issues: Vec<SecurityIssue>,
pub critical_count: usize,
pub error_count: usize,
pub warning_count: usize,
pub recommendations: Vec<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SecurityAuditStatus {
Passed,
Warning,
Failed,
Critical,
}
impl SecurityAuditReport {
pub fn print_report(&self) {
println!("đ Security Audit Report");
println!("========================");
println!("Preset: {:?}", self.preset);
println!("Status: {}", self.status_emoji());
println!();
if self.issues.is_empty() {
println!("â
No security issues found!");
} else {
println!("đ Issues Summary:");
println!(" Critical: {}", self.critical_count);
println!(" Errors: {}", self.error_count);
println!(" Warnings: {}", self.warning_count);
println!();
for issue in &self.issues {
println!(
"{} {}: {}",
issue.severity.emoji(),
issue.component,
issue.description
);
println!(" đĄ {}", issue.suggestion);
println!();
}
}
if !self.recommendations.is_empty() {
println!("đ Recommendations:");
for rec in &self.recommendations {
println!(" {}", rec);
}
println!();
}
println!("{}", self.get_summary_message());
}
fn status_emoji(&self) -> &str {
match self.status {
SecurityAuditStatus::Passed => "â
Passed",
SecurityAuditStatus::Warning => "â ī¸ Warning",
SecurityAuditStatus::Failed => "â Failed",
SecurityAuditStatus::Critical => "đ¨ Critical",
}
}
fn get_summary_message(&self) -> String {
match self.status {
SecurityAuditStatus::Passed => "đ Security audit passed! Your configuration meets security requirements.".to_string(),
SecurityAuditStatus::Warning => "â ī¸ Security audit completed with warnings. Consider addressing the issues above.".to_string(),
SecurityAuditStatus::Failed => "â Security audit failed. Please address the errors before deploying to production.".to_string(),
SecurityAuditStatus::Critical => "đ¨ Critical security issues found! Do not deploy to production until these are resolved.".to_string(),
}
}
pub fn is_production_ready(&self) -> bool {
matches!(
self.status,
SecurityAuditStatus::Passed | SecurityAuditStatus::Warning
)
}
pub fn get_blocking_issues(&self) -> Vec<&SecurityIssue> {
self.issues
.iter()
.filter(|issue| issue.blocks_production)
.collect()
}
}
impl SecuritySeverity {
fn emoji(&self) -> &str {
match self {
SecuritySeverity::Info => "âšī¸ ",
SecuritySeverity::Warning => "â ī¸ ",
SecuritySeverity::Error => "â",
SecuritySeverity::Critical => "đ¨",
}
}
}
impl std::fmt::Display for SecuritySeverity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SecuritySeverity::Info => write!(f, "INFO"),
SecuritySeverity::Warning => write!(f, "WARNING"),
SecuritySeverity::Error => write!(f, "ERROR"),
SecuritySeverity::Critical => write!(f, "CRITICAL"),
}
}
}