auth_framework/config/
mod.rs

1//! Configuration types for the authentication framework.
2
3pub mod app_config;
4pub mod config_manager;
5
6// Re-export for easy access
7pub use config_manager::{
8    AuthFrameworkSettings, ConfigBuilder, ConfigIntegration, ConfigManager, SessionCookieSettings,
9    SessionSettings,
10};
11
12use crate::errors::{AuthError, Result};
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15use std::time::Duration;
16
17/// Main configuration for the authentication framework.
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct AuthConfig {
20    /// Default token lifetime
21    pub token_lifetime: Duration,
22
23    /// Refresh token lifetime
24    pub refresh_token_lifetime: Duration,
25
26    /// Whether multi-factor authentication is enabled
27    pub enable_multi_factor: bool,
28
29    /// JWT issuer for token validation
30    pub issuer: String,
31
32    /// JWT audience for token validation
33    pub audience: String,
34
35    /// JWT secret key (optional - can be set via environment)
36    pub secret: Option<String>,
37
38    /// Storage configuration
39    pub storage: StorageConfig,
40
41    /// Rate limiting configuration
42    pub rate_limiting: RateLimitConfig,
43
44    /// Security configuration
45    pub security: SecurityConfig,
46
47    /// Audit logging configuration
48    pub audit: AuditConfig,
49
50    /// Custom settings for different auth methods
51    pub method_configs: HashMap<String, serde_json::Value>,
52}
53
54/// Storage configuration options.
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub enum StorageConfig {
57    /// In-memory storage (not recommended for production)
58    Memory,
59
60    /// Redis storage
61    #[cfg(feature = "redis-storage")]
62    Redis { url: String, key_prefix: String },
63
64    /// PostgreSQL storage
65    #[cfg(feature = "postgres-storage")]
66    Postgres {
67        connection_string: String,
68        table_prefix: String,
69    },
70
71    /// MySQL storage
72    #[cfg(feature = "mysql-storage")]
73    MySQL {
74        connection_string: String,
75        table_prefix: String,
76    },
77
78    /// Custom storage backend
79    Custom(String),
80}
81
82/// Rate limiting configuration.
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct RateLimitConfig {
85    /// Enable rate limiting
86    pub enabled: bool,
87
88    /// Maximum requests per window
89    pub max_requests: u32,
90
91    /// Time window for rate limiting
92    pub window: Duration,
93
94    /// Burst allowance
95    pub burst: u32,
96}
97
98/// Security configuration options.
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct SecurityConfig {
101    /// Minimum password length
102    pub min_password_length: usize,
103
104    /// Require password complexity
105    pub require_password_complexity: bool,
106
107    /// Password hash algorithm
108    pub password_hash_algorithm: PasswordHashAlgorithm,
109
110    /// JWT signing algorithm
111    pub jwt_algorithm: JwtAlgorithm,
112
113    /// Secret key for signing (should be loaded from environment)
114    pub secret_key: Option<String>,
115
116    /// Enable secure cookies
117    pub secure_cookies: bool,
118
119    /// Cookie SameSite policy
120    pub cookie_same_site: CookieSameSite,
121
122    /// CSRF protection
123    pub csrf_protection: bool,
124
125    /// Session timeout
126    pub session_timeout: Duration,
127}
128
129/// Password hashing algorithms.
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub enum PasswordHashAlgorithm {
132    Argon2,
133    Bcrypt,
134    Scrypt,
135}
136
137/// JWT signing algorithms.
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub enum JwtAlgorithm {
140    HS256,
141    HS384,
142    HS512,
143    RS256,
144    RS384,
145    RS512,
146    ES256,
147    ES384,
148}
149
150/// Cookie SameSite policies.
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub enum CookieSameSite {
153    Strict,
154    Lax,
155    None,
156}
157
158/// Audit logging configuration.
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct AuditConfig {
161    /// Enable audit logging
162    pub enabled: bool,
163
164    /// Log successful authentications
165    pub log_success: bool,
166
167    /// Log failed authentications
168    pub log_failures: bool,
169
170    /// Log permission checks
171    pub log_permissions: bool,
172
173    /// Log token operations
174    pub log_tokens: bool,
175
176    /// Audit log storage
177    pub storage: AuditStorage,
178}
179
180/// Audit log storage options.
181#[derive(Debug, Clone, Serialize, Deserialize)]
182pub enum AuditStorage {
183    /// Standard logging (via tracing)
184    Tracing,
185
186    /// File-based storage
187    File { path: String },
188
189    /// Database storage
190    Database { connection_string: String },
191
192    /// External service
193    External { endpoint: String, api_key: String },
194}
195
196impl Default for AuthConfig {
197    fn default() -> Self {
198        Self {
199            token_lifetime: Duration::from_secs(3600), // 1 hour
200            refresh_token_lifetime: Duration::from_secs(86400 * 7), // 7 days
201            enable_multi_factor: false,
202            issuer: "auth-framework".to_string(),
203            audience: "api".to_string(),
204            secret: None,
205            storage: StorageConfig::Memory,
206            rate_limiting: RateLimitConfig::default(),
207            security: SecurityConfig::default(),
208            audit: AuditConfig::default(),
209            method_configs: HashMap::new(),
210        }
211    }
212}
213
214impl Default for RateLimitConfig {
215    fn default() -> Self {
216        Self {
217            enabled: true,
218            max_requests: 100,
219            window: Duration::from_secs(60), // 1 minute
220            burst: 10,
221        }
222    }
223}
224
225impl Default for SecurityConfig {
226    fn default() -> Self {
227        Self {
228            min_password_length: 8,
229            require_password_complexity: true,
230            password_hash_algorithm: PasswordHashAlgorithm::Argon2,
231            jwt_algorithm: JwtAlgorithm::HS256,
232            secret_key: None,
233            secure_cookies: true,
234            cookie_same_site: CookieSameSite::Lax,
235            csrf_protection: true,
236            session_timeout: Duration::from_secs(3600 * 24), // 24 hours
237        }
238    }
239}
240
241impl Default for AuditConfig {
242    fn default() -> Self {
243        Self {
244            enabled: true,
245            log_success: true,
246            log_failures: true,
247            log_permissions: true,
248            log_tokens: false, // Tokens can be sensitive
249            storage: AuditStorage::Tracing,
250        }
251    }
252}
253
254impl AuthConfig {
255    /// Create a new configuration with default values.
256    pub fn new() -> Self {
257        Self::default()
258    }
259
260    /// Set the token lifetime.
261    pub fn token_lifetime(mut self, lifetime: Duration) -> Self {
262        self.token_lifetime = lifetime;
263        self
264    }
265
266    /// Set the refresh token lifetime.
267    pub fn refresh_token_lifetime(mut self, lifetime: Duration) -> Self {
268        self.refresh_token_lifetime = lifetime;
269        self
270    }
271
272    /// Enable or disable multi-factor authentication.
273    pub fn enable_multi_factor(mut self, enabled: bool) -> Self {
274        self.enable_multi_factor = enabled;
275        self
276    }
277
278    /// Set the JWT issuer.
279    pub fn issuer(mut self, issuer: impl Into<String>) -> Self {
280        self.issuer = issuer.into();
281        self
282    }
283
284    /// Set the JWT audience.
285    pub fn audience(mut self, audience: impl Into<String>) -> Self {
286        self.audience = audience.into();
287        self
288    }
289
290    /// Set the JWT secret key.
291    pub fn secret(mut self, secret: impl Into<String>) -> Self {
292        self.secret = Some(secret.into());
293        self
294    }
295
296    /// Require MFA for all users.
297    pub fn require_mfa(mut self, required: bool) -> Self {
298        self.enable_multi_factor = required;
299        self
300    }
301
302    /// Enable caching.
303    pub fn enable_caching(self, _enabled: bool) -> Self {
304        // This would set a caching flag in a real implementation
305        self
306    }
307
308    /// Set maximum failed attempts.
309    pub fn max_failed_attempts(self, _max: u32) -> Self {
310        // This would set max failed attempts in security config
311        self
312    }
313
314    /// Enable RBAC.
315    pub fn enable_rbac(self, _enabled: bool) -> Self {
316        // This would enable role-based access control
317        self
318    }
319
320    /// Enable security audit.
321    pub fn enable_security_audit(self, _enabled: bool) -> Self {
322        // This would enable security auditing
323        self
324    }
325
326    /// Enable middleware.
327    pub fn enable_middleware(self, _enabled: bool) -> Self {
328        // This would enable middleware support
329        self
330    }
331
332    /// Set the storage configuration.
333    pub fn storage(mut self, storage: StorageConfig) -> Self {
334        self.storage = storage;
335        self
336    }
337
338    /// Configure Redis storage.
339    #[cfg(feature = "redis-storage")]
340    pub fn redis_storage(mut self, url: impl Into<String>) -> Self {
341        self.storage = StorageConfig::Redis {
342            url: url.into(),
343            key_prefix: "auth:".to_string(),
344        };
345        self
346    }
347
348    /// Set rate limiting configuration.
349    pub fn rate_limiting(mut self, config: RateLimitConfig) -> Self {
350        self.rate_limiting = config;
351        self
352    }
353
354    /// Set security configuration.
355    pub fn security(mut self, config: SecurityConfig) -> Self {
356        self.security = config;
357        self
358    }
359
360    /// Set audit configuration.
361    pub fn audit(mut self, config: AuditConfig) -> Self {
362        self.audit = config;
363        self
364    }
365
366    /// Add configuration for a specific auth method.
367    pub fn method_config(
368        mut self,
369        method_name: impl Into<String>,
370        config: impl Serialize,
371    ) -> Result<Self> {
372        let value = serde_json::to_value(config)
373            .map_err(|e| AuthError::config(format!("Failed to serialize method config: {e}")))?;
374
375        self.method_configs.insert(method_name.into(), value);
376        Ok(self)
377    }
378
379    /// Get configuration for a specific auth method.
380    pub fn get_method_config<T>(&self, method_name: &str) -> Result<Option<T>>
381    where
382        T: for<'de> Deserialize<'de>,
383    {
384        if let Some(value) = self.method_configs.get(method_name) {
385            let config = serde_json::from_value(value.clone()).map_err(|e| {
386                AuthError::config(format!("Failed to deserialize method config: {e}"))
387            })?;
388            Ok(Some(config))
389        } else {
390            Ok(None)
391        }
392    }
393
394    /// Validate the configuration.
395    pub fn validate(&self) -> Result<()> {
396        // Validate token lifetimes
397        if self.token_lifetime.as_secs() == 0 {
398            return Err(AuthError::config("Token lifetime must be greater than 0"));
399        }
400
401        if self.refresh_token_lifetime.as_secs() == 0 {
402            return Err(AuthError::config(
403                "Refresh token lifetime must be greater than 0",
404            ));
405        }
406
407        if self.refresh_token_lifetime <= self.token_lifetime {
408            return Err(AuthError::config(
409                "Refresh token lifetime must be greater than token lifetime",
410            ));
411        }
412
413        // Validate JWT secret configuration
414        self.validate_jwt_secret()?;
415
416        // Validate security settings
417        if self.security.min_password_length < 4 {
418            return Err(AuthError::config(
419                "Minimum password length must be at least 4 characters",
420            ));
421        }
422
423        // Enhanced security validation for production
424        if self.is_production_environment() && !self.is_test_environment() {
425            self.validate_production_security()?;
426        }
427
428        // Validate rate limiting
429        if self.rate_limiting.enabled && self.rate_limiting.max_requests == 0 {
430            return Err(AuthError::config(
431                "Rate limit max requests must be greater than 0 when enabled",
432            ));
433        }
434
435        // Validate storage configuration
436        self.validate_storage_config()?;
437
438        Ok(())
439    }
440
441    /// Validate JWT secret configuration for security
442    fn validate_jwt_secret(&self) -> Result<()> {
443        // Check multiple sources for JWT secret
444        let env_secret = std::env::var("JWT_SECRET").ok();
445        let jwt_secret = self
446            .security
447            .secret_key
448            .as_ref()
449            .or(self.secret.as_ref())
450            .or(env_secret.as_ref());
451
452        if let Some(secret) = jwt_secret {
453            if secret.len() < 32 {
454                return Err(AuthError::config(
455                    "JWT secret must be at least 32 characters for security. \
456                     Generate with: openssl rand -base64 32",
457                ));
458            }
459
460            // Check for common insecure patterns (but allow in test environments)
461            if !self.is_test_environment()
462                && (secret.contains("secret")
463                    || secret.contains("password")
464                    || secret.contains("123"))
465            {
466                return Err(AuthError::config(
467                    "JWT secret appears to contain common words or patterns. \
468                     Use a cryptographically secure random string.",
469                ));
470            }
471
472            // Warn if secret looks like it might be base64 but too short
473            if secret.len() < 44
474                && secret
475                    .chars()
476                    .all(|c| c.is_alphanumeric() || c == '+' || c == '/' || c == '=')
477            {
478                tracing::warn!(
479                    "JWT secret may be too short for optimal security. \
480                     Consider using at least 44 characters (32 bytes base64-encoded)."
481                );
482            }
483        } else if self.is_production_environment() {
484            return Err(AuthError::config(
485                "JWT secret is required for production environments. \
486                 Set JWT_SECRET environment variable or configure security.secret_key",
487            ));
488        }
489
490        Ok(())
491    }
492
493    /// Validate production-specific security requirements
494    fn validate_production_security(&self) -> Result<()> {
495        // Require strong password policies in production
496        if self.security.min_password_length < 8 {
497            return Err(AuthError::config(
498                "Production environments require minimum password length of 8 characters",
499            ));
500        }
501
502        if !self.security.require_password_complexity {
503            tracing::warn!("Production deployment should enable password complexity requirements");
504        }
505
506        // Require secure cookies in production
507        if !self.security.secure_cookies {
508            return Err(AuthError::config(
509                "Production environments must use secure cookies (HTTPS required)",
510            ));
511        }
512
513        // Ensure rate limiting is enabled
514        if !self.rate_limiting.enabled {
515            tracing::warn!("Production deployment should enable rate limiting for security");
516        }
517
518        // Validate audit configuration for compliance
519        if !self.audit.enabled {
520            return Err(AuthError::config(
521                "Production environments require audit logging for compliance",
522            ));
523        }
524
525        Ok(())
526    }
527
528    /// Validate storage configuration
529    fn validate_storage_config(&self) -> Result<()> {
530        match &self.storage {
531            StorageConfig::Memory => {
532                if self.is_production_environment() && !self.is_test_environment() {
533                    return Err(AuthError::config(
534                        "Memory storage is not suitable for production environments. \
535                         Use PostgreSQL, Redis, or MySQL storage.",
536                    ));
537                }
538            }
539            #[cfg(feature = "mysql-storage")]
540            StorageConfig::MySQL { .. } => {
541                tracing::warn!(
542                    "MySQL storage has known RSA vulnerability (RUSTSEC-2023-0071). \
543                     Consider using PostgreSQL for enhanced security."
544                );
545            }
546            _ => {} // PostgreSQL and Redis are production-ready
547        }
548
549        Ok(())
550    }
551
552    /// Detect production environment
553    fn is_production_environment(&self) -> bool {
554        // Check common production environment indicators
555        if let Ok(env) = std::env::var("ENVIRONMENT")
556            && (env.to_lowercase() == "production" || env.to_lowercase() == "prod")
557        {
558            return true;
559        }
560
561        if let Ok(env) = std::env::var("ENV")
562            && (env.to_lowercase() == "production" || env.to_lowercase() == "prod")
563        {
564            return true;
565        }
566
567        if let Ok(env) = std::env::var("NODE_ENV")
568            && env.to_lowercase() == "production"
569        {
570            return true;
571        }
572
573        if let Ok(env) = std::env::var("RUST_ENV")
574            && env.to_lowercase() == "production"
575        {
576            return true;
577        }
578
579        // Check for containerized environments
580        if std::env::var("KUBERNETES_SERVICE_HOST").is_ok() {
581            return true;
582        }
583
584        if std::env::var("DOCKER_CONTAINER").is_ok() {
585            return true;
586        }
587
588        false
589    }
590
591    /// Detect test environment
592    fn is_test_environment(&self) -> bool {
593        // Check if we're running in a test environment
594        cfg!(test)
595            || std::thread::current()
596                .name()
597                .is_some_and(|name| name.contains("test"))
598            || std::env::var("RUST_TEST").is_ok()
599            || std::env::var("ENVIRONMENT").as_deref() == Ok("test")
600            || std::env::var("ENV").as_deref() == Ok("test")
601            || std::env::args().any(|arg| arg.contains("test"))
602    }
603}
604
605impl RateLimitConfig {
606    /// Create a new rate limit configuration.
607    pub fn new(max_requests: u32, window: Duration) -> Self {
608        Self {
609            enabled: true,
610            max_requests,
611            window,
612            burst: max_requests / 10, // 10% of max as burst
613        }
614    }
615
616    /// Disable rate limiting.
617    pub fn disabled() -> Self {
618        Self {
619            enabled: false,
620            ..Default::default()
621        }
622    }
623}
624
625impl SecurityConfig {
626    /// Create a new security configuration with secure defaults.
627    pub fn secure() -> Self {
628        Self {
629            min_password_length: 12,
630            require_password_complexity: true,
631            password_hash_algorithm: PasswordHashAlgorithm::Argon2,
632            jwt_algorithm: JwtAlgorithm::RS256,
633            secret_key: None,
634            secure_cookies: true,
635            cookie_same_site: CookieSameSite::Strict,
636            csrf_protection: true,
637            session_timeout: Duration::from_secs(3600 * 8), // 8 hours
638        }
639    }
640
641    /// Create a development-friendly configuration.
642    /// WARNING: You MUST set a secret key before using this configuration!
643    /// Use either config.security.secret_key or JWT_SECRET environment variable.
644    pub fn development() -> Self {
645        Self {
646            min_password_length: 6,
647            require_password_complexity: false,
648            password_hash_algorithm: PasswordHashAlgorithm::Bcrypt,
649            jwt_algorithm: JwtAlgorithm::HS256,
650            secret_key: None, // Must be set by developer for security
651            secure_cookies: false,
652            cookie_same_site: CookieSameSite::Lax,
653            csrf_protection: false,
654            session_timeout: Duration::from_secs(3600 * 24), // 24 hours
655        }
656    }
657}