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 IP-based rate limiting
86    pub enabled: bool,
87
88    /// Maximum requests per window (per IP)
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    /// Enable per-user rate limiting (in addition to IP-based)
98    pub per_user_enabled: bool,
99
100    /// Maximum requests per user per window
101    pub max_requests_per_user: u32,
102
103    /// Time window for per-user rate limiting
104    pub per_user_window: Duration,
105}
106
107/// Security configuration options.
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct SecurityConfig {
110    /// Minimum password length (recommended: 12+ for strong security)
111    pub min_password_length: usize,
112
113    /// Require password complexity
114    pub require_password_complexity: bool,
115
116    /// Require uppercase letters in password
117    pub require_uppercase: bool,
118
119    /// Require lowercase letters in password
120    pub require_lowercase: bool,
121
122    /// Require digits in password
123    pub require_digit: bool,
124
125    /// Require special characters in password
126    pub require_special: bool,
127
128    /// Minimum number of complexity criteria that must be met
129    pub min_complexity_criteria: usize,
130
131    /// Password hash algorithm
132    pub password_hash_algorithm: PasswordHashAlgorithm,
133
134    /// JWT signing algorithm
135    pub jwt_algorithm: JwtAlgorithm,
136
137    /// Secret key for signing (should be loaded from environment)
138    pub secret_key: Option<String>,
139
140    /// Enable secure cookies
141    pub secure_cookies: bool,
142
143    /// Cookie SameSite policy
144    pub cookie_same_site: CookieSameSite,
145
146    /// CSRF protection
147    pub csrf_protection: bool,
148
149    /// Session timeout
150    pub session_timeout: Duration,
151
152    /// Account lockout configuration
153    pub lockout: LockoutConfig,
154
155    /// Maximum API keys per user (0 = unlimited)
156    pub max_api_keys_per_user: usize,
157
158    /// OAuth2 configuration
159    pub oauth2: OAuth2SecurityConfig,
160}
161
162/// Password hashing algorithms.
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub enum PasswordHashAlgorithm {
165    Argon2,
166    Bcrypt,
167    Scrypt,
168}
169
170/// JWT signing algorithms.
171#[derive(Debug, Clone, Serialize, Deserialize)]
172pub enum JwtAlgorithm {
173    HS256,
174    HS384,
175    HS512,
176    RS256,
177    RS384,
178    RS512,
179    ES256,
180    ES384,
181}
182
183/// Cookie SameSite policies.
184#[derive(Debug, Clone, Serialize, Deserialize)]
185pub enum CookieSameSite {
186    Strict,
187    Lax,
188    None,
189}
190
191/// Account lockout configuration for failed login attempts.
192#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct LockoutConfig {
194    /// Enable account lockout
195    pub enabled: bool,
196
197    /// Maximum failed login attempts before lockout
198    pub max_failed_attempts: u32,
199
200    /// Lockout duration in seconds
201    pub lockout_duration_seconds: u64,
202
203    /// Progressive lockout (increase duration with repeated lockouts)
204    pub progressive_lockout: bool,
205
206    /// Maximum lockout duration in seconds (for progressive lockout)
207    pub max_lockout_duration_seconds: u64,
208
209    /// Track failed attempts window in seconds
210    pub tracking_window_seconds: u64,
211}
212
213/// OAuth2 security configuration.
214#[derive(Debug, Clone, Serialize, Deserialize)]
215pub struct OAuth2SecurityConfig {
216    /// Require user authentication before OAuth2 authorize
217    pub require_user_authentication: bool,
218
219    /// Validate redirect_uri against registered client URIs
220    pub validate_redirect_uri: bool,
221
222    /// Require client_secret for token exchange
223    pub require_client_secret: bool,
224
225    /// Enable PKCE (Proof Key for Code Exchange)
226    pub require_pkce: bool,
227}
228
229/// OAuth2 client registration information.
230#[derive(Debug, Clone, Serialize, Deserialize)]
231pub struct OAuth2Client {
232    /// Client ID
233    pub client_id: String,
234
235    /// Client secret (hashed)
236    pub client_secret_hash: Option<String>,
237
238    /// Client name
239    pub name: String,
240
241    /// Registered redirect URIs (whitelist)
242    pub redirect_uris: Vec<String>,
243
244    /// Allowed grant types
245    pub grant_types: Vec<String>,
246
247    /// Allowed scopes
248    pub scopes: Vec<String>,
249
250    /// Client is active
251    pub active: bool,
252
253    /// Creation timestamp
254    pub created_at: String,
255
256    /// Updated timestamp
257    pub updated_at: String,
258}
259
260/// Audit logging configuration.
261#[derive(Debug, Clone, Serialize, Deserialize)]
262pub struct AuditConfig {
263    /// Enable audit logging
264    pub enabled: bool,
265
266    /// Log successful authentications
267    pub log_success: bool,
268
269    /// Log failed authentications
270    pub log_failures: bool,
271
272    /// Log permission checks
273    pub log_permissions: bool,
274
275    /// Log token operations
276    pub log_tokens: bool,
277
278    /// Audit log storage
279    pub storage: AuditStorage,
280}
281
282/// Audit log storage options.
283#[derive(Debug, Clone, Serialize, Deserialize)]
284pub enum AuditStorage {
285    /// Standard logging (via tracing)
286    Tracing,
287
288    /// File-based storage
289    File { path: String },
290
291    /// Database storage
292    Database { connection_string: String },
293
294    /// External service
295    External { endpoint: String, api_key: String },
296}
297
298impl Default for AuthConfig {
299    fn default() -> Self {
300        Self {
301            token_lifetime: Duration::from_secs(3600), // 1 hour
302            refresh_token_lifetime: Duration::from_secs(86400 * 7), // 7 days
303            enable_multi_factor: false,
304            issuer: "auth-framework".to_string(),
305            audience: "api".to_string(),
306            secret: None,
307            storage: StorageConfig::Memory,
308            rate_limiting: RateLimitConfig::default(),
309            security: SecurityConfig::default(),
310            audit: AuditConfig::default(),
311            method_configs: HashMap::new(),
312        }
313    }
314}
315
316impl Default for RateLimitConfig {
317    fn default() -> Self {
318        Self {
319            enabled: true,
320            max_requests: 100,
321            window: Duration::from_secs(60), // 1 minute
322            burst: 10,
323            per_user_enabled: true,
324            max_requests_per_user: 120, // Slightly higher for authenticated users
325            per_user_window: Duration::from_secs(60),
326        }
327    }
328}
329
330impl Default for SecurityConfig {
331    fn default() -> Self {
332        Self {
333            min_password_length: 12, // Increased from 8 for better security
334            require_password_complexity: true,
335            require_uppercase: true,
336            require_lowercase: true,
337            require_digit: true,
338            require_special: true,
339            min_complexity_criteria: 3, // At least 3 of 4 criteria must be met
340            password_hash_algorithm: PasswordHashAlgorithm::Argon2,
341            jwt_algorithm: JwtAlgorithm::HS256,
342            secret_key: None,
343            secure_cookies: true,
344            cookie_same_site: CookieSameSite::Lax,
345            csrf_protection: true,
346            session_timeout: Duration::from_secs(3600 * 24), // 24 hours
347            lockout: LockoutConfig::default(),
348            max_api_keys_per_user: 10, // Reasonable default limit
349            oauth2: OAuth2SecurityConfig::default(),
350        }
351    }
352}
353
354impl Default for AuditConfig {
355    fn default() -> Self {
356        Self {
357            enabled: true,
358            log_success: true,
359            log_failures: true,
360            log_permissions: true,
361            log_tokens: false, // Tokens can be sensitive
362            storage: AuditStorage::Tracing,
363        }
364    }
365}
366
367impl Default for LockoutConfig {
368    fn default() -> Self {
369        Self {
370            enabled: true,
371            max_failed_attempts: 5,
372            lockout_duration_seconds: 900, // 15 minutes
373            progressive_lockout: true,
374            max_lockout_duration_seconds: 3600, // 1 hour max
375            tracking_window_seconds: 300,       // 5 minutes
376        }
377    }
378}
379
380impl Default for OAuth2SecurityConfig {
381    fn default() -> Self {
382        Self {
383            require_user_authentication: true, // Security best practice
384            validate_redirect_uri: true,       // Prevent open redirects
385            require_client_secret: true,       // Security best practice
386            require_pkce: true,                // Modern OAuth2 security
387        }
388    }
389}
390
391impl AuthConfig {
392    /// Create a new configuration with default values.
393    pub fn new() -> Self {
394        Self::default()
395    }
396
397    /// Set the token lifetime.
398    pub fn token_lifetime(mut self, lifetime: Duration) -> Self {
399        self.token_lifetime = lifetime;
400        self
401    }
402
403    /// Set the refresh token lifetime.
404    pub fn refresh_token_lifetime(mut self, lifetime: Duration) -> Self {
405        self.refresh_token_lifetime = lifetime;
406        self
407    }
408
409    /// Enable or disable multi-factor authentication.
410    pub fn enable_multi_factor(mut self, enabled: bool) -> Self {
411        self.enable_multi_factor = enabled;
412        self
413    }
414
415    /// Set the JWT issuer.
416    pub fn issuer(mut self, issuer: impl Into<String>) -> Self {
417        self.issuer = issuer.into();
418        self
419    }
420
421    /// Set the JWT audience.
422    pub fn audience(mut self, audience: impl Into<String>) -> Self {
423        self.audience = audience.into();
424        self
425    }
426
427    /// Set the JWT secret key.
428    pub fn secret(mut self, secret: impl Into<String>) -> Self {
429        self.secret = Some(secret.into());
430        self
431    }
432
433    /// Require MFA for all users.
434    pub fn require_mfa(mut self, required: bool) -> Self {
435        self.enable_multi_factor = required;
436        self
437    }
438
439    /// Enable caching.
440    pub fn enable_caching(self, _enabled: bool) -> Self {
441        // This would set a caching flag in a real implementation
442        self
443    }
444
445    /// Set maximum failed attempts.
446    pub fn max_failed_attempts(self, _max: u32) -> Self {
447        // This would set max failed attempts in security config
448        self
449    }
450
451    /// Enable RBAC.
452    pub fn enable_rbac(self, _enabled: bool) -> Self {
453        // This would enable role-based access control
454        self
455    }
456
457    /// Enable security audit.
458    pub fn enable_security_audit(self, _enabled: bool) -> Self {
459        // This would enable security auditing
460        self
461    }
462
463    /// Enable middleware.
464    pub fn enable_middleware(self, _enabled: bool) -> Self {
465        // This would enable middleware support
466        self
467    }
468
469    /// Set the storage configuration.
470    pub fn storage(mut self, storage: StorageConfig) -> Self {
471        self.storage = storage;
472        self
473    }
474
475    /// Configure Redis storage.
476    #[cfg(feature = "redis-storage")]
477    pub fn redis_storage(mut self, url: impl Into<String>) -> Self {
478        self.storage = StorageConfig::Redis {
479            url: url.into(),
480            key_prefix: "auth:".to_string(),
481        };
482        self
483    }
484
485    /// Set rate limiting configuration.
486    pub fn rate_limiting(mut self, config: RateLimitConfig) -> Self {
487        self.rate_limiting = config;
488        self
489    }
490
491    /// Set security configuration.
492    pub fn security(mut self, config: SecurityConfig) -> Self {
493        self.security = config;
494        self
495    }
496
497    /// Set audit configuration.
498    pub fn audit(mut self, config: AuditConfig) -> Self {
499        self.audit = config;
500        self
501    }
502
503    /// Add configuration for a specific auth method.
504    pub fn method_config(
505        mut self,
506        method_name: impl Into<String>,
507        config: impl Serialize,
508    ) -> Result<Self> {
509        let value = serde_json::to_value(config)
510            .map_err(|e| AuthError::config(format!("Failed to serialize method config: {e}")))?;
511
512        self.method_configs.insert(method_name.into(), value);
513        Ok(self)
514    }
515
516    /// Get configuration for a specific auth method.
517    pub fn get_method_config<T>(&self, method_name: &str) -> Result<Option<T>>
518    where
519        T: for<'de> Deserialize<'de>,
520    {
521        if let Some(value) = self.method_configs.get(method_name) {
522            let config = serde_json::from_value(value.clone()).map_err(|e| {
523                AuthError::config(format!("Failed to deserialize method config: {e}"))
524            })?;
525            Ok(Some(config))
526        } else {
527            Ok(None)
528        }
529    }
530
531    /// Validate the configuration.
532    pub fn validate(&self) -> Result<()> {
533        // Validate token lifetimes
534        if self.token_lifetime.as_secs() == 0 {
535            return Err(AuthError::config("Token lifetime must be greater than 0"));
536        }
537
538        if self.refresh_token_lifetime.as_secs() == 0 {
539            return Err(AuthError::config(
540                "Refresh token lifetime must be greater than 0",
541            ));
542        }
543
544        if self.refresh_token_lifetime <= self.token_lifetime {
545            return Err(AuthError::config(
546                "Refresh token lifetime must be greater than token lifetime",
547            ));
548        }
549
550        // Validate JWT secret configuration
551        self.validate_jwt_secret()?;
552
553        // Validate security settings
554        if self.security.min_password_length < 4 {
555            return Err(AuthError::config(
556                "Minimum password length must be at least 4 characters",
557            ));
558        }
559
560        // Enhanced security validation for production
561        if self.is_production_environment() && !self.is_test_environment() {
562            self.validate_production_security()?;
563        }
564
565        // Validate rate limiting
566        if self.rate_limiting.enabled && self.rate_limiting.max_requests == 0 {
567            return Err(AuthError::config(
568                "Rate limit max requests must be greater than 0 when enabled",
569            ));
570        }
571
572        // Validate storage configuration
573        self.validate_storage_config()?;
574
575        Ok(())
576    }
577
578    /// Validate JWT secret configuration for security
579    fn validate_jwt_secret(&self) -> Result<()> {
580        // Check multiple sources for JWT secret
581        let env_secret = std::env::var("JWT_SECRET").ok();
582        let jwt_secret = self
583            .security
584            .secret_key
585            .as_ref()
586            .or(self.secret.as_ref())
587            .or(env_secret.as_ref());
588
589        if let Some(secret) = jwt_secret {
590            if secret.len() < 32 {
591                return Err(AuthError::config(
592                    "JWT secret must be at least 32 characters for security. \
593                     Generate with: openssl rand -base64 32",
594                ));
595            }
596
597            // Check for common insecure patterns (but allow in test environments)
598            if !self.is_test_environment()
599                && (secret.contains("secret")
600                    || secret.contains("password")
601                    || secret.contains("123"))
602            {
603                return Err(AuthError::config(
604                    "JWT secret appears to contain common words or patterns. \
605                     Use a cryptographically secure random string.",
606                ));
607            }
608
609            // Warn if secret looks like it might be base64 but too short
610            if secret.len() < 44
611                && secret
612                    .chars()
613                    .all(|c| c.is_alphanumeric() || c == '+' || c == '/' || c == '=')
614            {
615                tracing::warn!(
616                    "JWT secret may be too short for optimal security. \
617                     Consider using at least 44 characters (32 bytes base64-encoded)."
618                );
619            }
620        } else if self.is_production_environment() {
621            return Err(AuthError::config(
622                "JWT secret is required for production environments. \
623                 Set JWT_SECRET environment variable or configure security.secret_key",
624            ));
625        }
626
627        Ok(())
628    }
629
630    /// Validate production-specific security requirements
631    fn validate_production_security(&self) -> Result<()> {
632        // Require strong password policies in production
633        if self.security.min_password_length < 8 {
634            return Err(AuthError::config(
635                "Production environments require minimum password length of 8 characters",
636            ));
637        }
638
639        if !self.security.require_password_complexity {
640            tracing::warn!("Production deployment should enable password complexity requirements");
641        }
642
643        // Require secure cookies in production
644        if !self.security.secure_cookies {
645            return Err(AuthError::config(
646                "Production environments must use secure cookies (HTTPS required)",
647            ));
648        }
649
650        // Ensure rate limiting is enabled
651        if !self.rate_limiting.enabled {
652            tracing::warn!("Production deployment should enable rate limiting for security");
653        }
654
655        // Validate audit configuration for compliance
656        if !self.audit.enabled {
657            return Err(AuthError::config(
658                "Production environments require audit logging for compliance",
659            ));
660        }
661
662        Ok(())
663    }
664
665    /// Validate storage configuration
666    fn validate_storage_config(&self) -> Result<()> {
667        match &self.storage {
668            StorageConfig::Memory => {
669                if self.is_production_environment() && !self.is_test_environment() {
670                    return Err(AuthError::config(
671                        "Memory storage is not suitable for production environments. \
672                         Use PostgreSQL, Redis, or MySQL storage.",
673                    ));
674                }
675            }
676            #[cfg(feature = "mysql-storage")]
677            StorageConfig::MySQL { .. } => {
678                tracing::warn!(
679                    "MySQL storage has known RSA vulnerability (RUSTSEC-2023-0071). \
680                     Consider using PostgreSQL for enhanced security."
681                );
682            }
683            _ => {} // PostgreSQL and Redis are production-ready
684        }
685
686        Ok(())
687    }
688
689    /// Detect production environment
690    fn is_production_environment(&self) -> bool {
691        // Check common production environment indicators
692        if let Ok(env) = std::env::var("ENVIRONMENT")
693            && (env.to_lowercase() == "production" || env.to_lowercase() == "prod")
694        {
695            return true;
696        }
697
698        if let Ok(env) = std::env::var("ENV")
699            && (env.to_lowercase() == "production" || env.to_lowercase() == "prod")
700        {
701            return true;
702        }
703
704        if let Ok(env) = std::env::var("NODE_ENV")
705            && env.to_lowercase() == "production"
706        {
707            return true;
708        }
709
710        if let Ok(env) = std::env::var("RUST_ENV")
711            && env.to_lowercase() == "production"
712        {
713            return true;
714        }
715
716        // Check for containerized environments
717        if std::env::var("KUBERNETES_SERVICE_HOST").is_ok() {
718            return true;
719        }
720
721        if std::env::var("DOCKER_CONTAINER").is_ok() {
722            return true;
723        }
724
725        false
726    }
727
728    /// Detect test environment
729    fn is_test_environment(&self) -> bool {
730        // Check if we're running in a test environment
731        cfg!(test)
732            || std::thread::current()
733                .name()
734                .is_some_and(|name| name.contains("test"))
735            || std::env::var("RUST_TEST").is_ok()
736            || std::env::var("ENVIRONMENT").as_deref() == Ok("test")
737            || std::env::var("ENV").as_deref() == Ok("test")
738            || std::env::args().any(|arg| arg.contains("test"))
739    }
740}
741
742impl RateLimitConfig {
743    /// Create a new rate limit configuration.
744    pub fn new(max_requests: u32, window: Duration) -> Self {
745        Self {
746            enabled: true,
747            max_requests,
748            window,
749            burst: max_requests / 10, // 10% of max as burst
750            per_user_enabled: true,
751            max_requests_per_user: max_requests + 20,
752            per_user_window: window,
753        }
754    }
755
756    /// Disable rate limiting.
757    pub fn disabled() -> Self {
758        Self {
759            enabled: false,
760            per_user_enabled: false,
761            ..Default::default()
762        }
763    }
764}
765
766impl SecurityConfig {
767    /// Create a new security configuration with secure defaults.
768    pub fn secure() -> Self {
769        Self {
770            min_password_length: 12,
771            require_password_complexity: true,
772            require_uppercase: true,
773            require_lowercase: true,
774            require_digit: true,
775            require_special: true,
776            min_complexity_criteria: 4, // All criteria for maximum security
777            password_hash_algorithm: PasswordHashAlgorithm::Argon2,
778            jwt_algorithm: JwtAlgorithm::RS256,
779            secret_key: None,
780            secure_cookies: true,
781            cookie_same_site: CookieSameSite::Strict,
782            csrf_protection: true,
783            session_timeout: Duration::from_secs(3600 * 8), // 8 hours
784            lockout: LockoutConfig::default(),
785            max_api_keys_per_user: 5, // Stricter limit for production
786            oauth2: OAuth2SecurityConfig::default(),
787        }
788    }
789
790    /// Create a development-friendly configuration.
791    /// WARNING: You MUST set a secret key before using this configuration!
792    /// Use either config.security.secret_key or JWT_SECRET environment variable.
793    pub fn development() -> Self {
794        Self {
795            min_password_length: 6,
796            require_password_complexity: false,
797            require_uppercase: false,
798            require_lowercase: false,
799            require_digit: false,
800            require_special: false,
801            min_complexity_criteria: 0, // Relaxed for development
802            password_hash_algorithm: PasswordHashAlgorithm::Bcrypt,
803            jwt_algorithm: JwtAlgorithm::HS256,
804            secret_key: None, // Must be set by developer for security
805            secure_cookies: false,
806            cookie_same_site: CookieSameSite::Lax,
807            csrf_protection: false,
808            session_timeout: Duration::from_secs(3600 * 24), // 24 hours
809            lockout: LockoutConfig {
810                enabled: false, // Disabled for development convenience
811                ..Default::default()
812            },
813            max_api_keys_per_user: 0, // Unlimited for development
814            oauth2: OAuth2SecurityConfig {
815                require_user_authentication: false, // Relaxed for testing
816                validate_redirect_uri: false,
817                require_client_secret: false,
818                require_pkce: false,
819            },
820        }
821    }
822}