elif_auth/
config.rs

1//! Authentication configuration types and utilities
2
3use serde::{Deserialize, Serialize};
4
5/// Main authentication configuration
6#[derive(Debug, Clone, Serialize, Deserialize, Default)]
7pub struct AuthConfig {
8    /// JWT configuration
9    pub jwt: JwtConfig,
10
11    /// Session configuration
12    pub session: SessionConfig,
13
14    /// Password policy configuration
15    pub password: PasswordConfig,
16
17    /// Multi-factor authentication configuration
18    pub mfa: MfaConfig,
19
20    /// Rate limiting for authentication attempts
21    pub rate_limit: AuthRateLimitConfig,
22}
23
24/// JWT token configuration
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct JwtConfig {
27    /// Secret key for JWT signing (HS256) or path to private key (RS256)
28    pub secret: String,
29
30    /// JWT signing algorithm (HS256, HS384, HS512, RS256, RS384, RS512)
31    #[serde(default = "default_jwt_algorithm")]
32    pub algorithm: String,
33
34    /// Access token expiration time in seconds
35    #[serde(default = "default_access_token_expiry")]
36    pub access_token_expiry: u64,
37
38    /// Refresh token expiration time in seconds  
39    #[serde(default = "default_refresh_token_expiry")]
40    pub refresh_token_expiry: u64,
41
42    /// JWT issuer
43    #[serde(default = "default_jwt_issuer")]
44    pub issuer: String,
45
46    /// JWT audience
47    pub audience: Option<String>,
48
49    /// Allow token refresh
50    #[serde(default = "default_true")]
51    pub allow_refresh: bool,
52}
53
54/// Session configuration
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct SessionConfig {
57    /// Session storage backend (memory, database, redis)
58    #[serde(default = "default_session_storage")]
59    pub storage: String,
60
61    /// Session expiration time in seconds
62    #[serde(default = "default_session_expiry")]
63    pub expiry: u64,
64
65    /// Session cookie name
66    #[serde(default = "default_session_cookie_name")]
67    pub cookie_name: String,
68
69    /// Session cookie domain
70    pub cookie_domain: Option<String>,
71
72    /// Session cookie path
73    #[serde(default = "default_session_cookie_path")]
74    pub cookie_path: String,
75
76    /// Session cookie secure flag
77    #[serde(default = "default_false")]
78    pub cookie_secure: bool,
79
80    /// Session cookie HTTP-only flag
81    #[serde(default = "default_true")]
82    pub cookie_http_only: bool,
83
84    /// Session cookie SameSite policy
85    #[serde(default = "default_session_cookie_same_site")]
86    pub cookie_same_site: String,
87
88    /// Session cleanup interval in seconds
89    #[serde(default = "default_session_cleanup_interval")]
90    pub cleanup_interval: u64,
91}
92
93/// Password policy configuration
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct PasswordConfig {
96    /// Minimum password length
97    #[serde(default = "default_min_password_length")]
98    pub min_length: usize,
99
100    /// Maximum password length
101    #[serde(default = "default_max_password_length")]
102    pub max_length: usize,
103
104    /// Require uppercase letters
105    #[serde(default = "default_true")]
106    pub require_uppercase: bool,
107
108    /// Require lowercase letters  
109    #[serde(default = "default_true")]
110    pub require_lowercase: bool,
111
112    /// Require numbers
113    #[serde(default = "default_true")]
114    pub require_numbers: bool,
115
116    /// Require special characters
117    #[serde(default = "default_false")]
118    pub require_special: bool,
119
120    /// Password hashing algorithm (argon2, bcrypt)
121    #[serde(default = "default_hash_algorithm")]
122    pub hash_algorithm: String,
123
124    /// Bcrypt cost factor (if using bcrypt)
125    #[serde(default = "default_bcrypt_cost")]
126    pub bcrypt_cost: u32,
127
128    /// Argon2 memory cost in KB (if using argon2)
129    #[serde(default = "default_argon2_memory")]
130    pub argon2_memory: u32,
131
132    /// Argon2 time cost (iterations)
133    #[serde(default = "default_argon2_iterations")]
134    pub argon2_iterations: u32,
135
136    /// Argon2 parallelism factor
137    #[serde(default = "default_argon2_parallelism")]
138    pub argon2_parallelism: u32,
139}
140
141/// Multi-factor authentication configuration
142#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct MfaConfig {
144    /// Enable MFA
145    #[serde(default = "default_false")]
146    pub enabled: bool,
147
148    /// TOTP issuer name
149    #[serde(default = "default_totp_issuer")]
150    pub totp_issuer: String,
151
152    /// TOTP time step in seconds
153    #[serde(default = "default_totp_step")]
154    pub totp_step: u64,
155
156    /// TOTP code length
157    #[serde(default = "default_totp_digits")]
158    pub totp_digits: usize,
159
160    /// TOTP time window tolerance
161    #[serde(default = "default_totp_window")]
162    pub totp_window: u8,
163
164    /// Number of backup codes to generate
165    #[serde(default = "default_backup_codes_count")]
166    pub backup_codes_count: usize,
167
168    /// Backup code length
169    #[serde(default = "default_backup_code_length")]
170    pub backup_code_length: usize,
171}
172
173/// Authentication rate limiting configuration
174#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct AuthRateLimitConfig {
176    /// Maximum login attempts per IP
177    #[serde(default = "default_max_attempts")]
178    pub max_attempts: u32,
179
180    /// Time window for rate limiting in seconds
181    #[serde(default = "default_rate_limit_window")]
182    pub window_seconds: u64,
183
184    /// Lockout duration in seconds after max attempts
185    #[serde(default = "default_lockout_duration")]
186    pub lockout_duration: u64,
187}
188
189// Default value functions
190fn default_jwt_algorithm() -> String {
191    "HS256".to_string()
192}
193fn default_access_token_expiry() -> u64 {
194    15 * 60
195} // 15 minutes
196fn default_refresh_token_expiry() -> u64 {
197    7 * 24 * 60 * 60
198} // 7 days
199fn default_jwt_issuer() -> String {
200    "elif.rs".to_string()
201}
202fn default_session_storage() -> String {
203    "memory".to_string()
204}
205fn default_session_expiry() -> u64 {
206    24 * 60 * 60
207} // 24 hours
208fn default_session_cookie_name() -> String {
209    "elif_session".to_string()
210}
211fn default_session_cookie_path() -> String {
212    "/".to_string()
213}
214fn default_session_cookie_same_site() -> String {
215    "Lax".to_string()
216}
217fn default_session_cleanup_interval() -> u64 {
218    60 * 60
219} // 1 hour
220fn default_min_password_length() -> usize {
221    8
222}
223fn default_max_password_length() -> usize {
224    128
225}
226fn default_hash_algorithm() -> String {
227    "argon2".to_string()
228}
229fn default_bcrypt_cost() -> u32 {
230    12
231}
232fn default_argon2_memory() -> u32 {
233    65536
234} // 64MB
235fn default_argon2_iterations() -> u32 {
236    3
237}
238fn default_argon2_parallelism() -> u32 {
239    4
240}
241fn default_totp_issuer() -> String {
242    "elif.rs".to_string()
243}
244fn default_totp_step() -> u64 {
245    30
246}
247fn default_totp_digits() -> usize {
248    6
249}
250fn default_totp_window() -> u8 {
251    1
252}
253fn default_backup_codes_count() -> usize {
254    10
255}
256fn default_backup_code_length() -> usize {
257    8
258}
259fn default_max_attempts() -> u32 {
260    5
261}
262fn default_rate_limit_window() -> u64 {
263    15 * 60
264} // 15 minutes
265fn default_lockout_duration() -> u64 {
266    30 * 60
267} // 30 minutes
268fn default_true() -> bool {
269    true
270}
271fn default_false() -> bool {
272    false
273}
274
275impl Default for JwtConfig {
276    fn default() -> Self {
277        Self {
278            secret: "default-secret-key-change-in-production-32-chars-long".to_string(), // 32+ chars for validation
279            algorithm: default_jwt_algorithm(),
280            access_token_expiry: default_access_token_expiry(),
281            refresh_token_expiry: default_refresh_token_expiry(),
282            issuer: default_jwt_issuer(),
283            audience: None,
284            allow_refresh: default_true(),
285        }
286    }
287}
288
289impl Default for SessionConfig {
290    fn default() -> Self {
291        Self {
292            storage: default_session_storage(),
293            expiry: default_session_expiry(),
294            cookie_name: default_session_cookie_name(),
295            cookie_domain: None,
296            cookie_path: default_session_cookie_path(),
297            cookie_secure: default_false(),
298            cookie_http_only: default_true(),
299            cookie_same_site: default_session_cookie_same_site(),
300            cleanup_interval: default_session_cleanup_interval(),
301        }
302    }
303}
304
305impl Default for PasswordConfig {
306    fn default() -> Self {
307        Self {
308            min_length: default_min_password_length(),
309            max_length: default_max_password_length(),
310            require_uppercase: default_true(),
311            require_lowercase: default_true(),
312            require_numbers: default_true(),
313            require_special: default_false(),
314            hash_algorithm: default_hash_algorithm(),
315            bcrypt_cost: default_bcrypt_cost(),
316            argon2_memory: default_argon2_memory(),
317            argon2_iterations: default_argon2_iterations(),
318            argon2_parallelism: default_argon2_parallelism(),
319        }
320    }
321}
322
323impl Default for MfaConfig {
324    fn default() -> Self {
325        Self {
326            enabled: default_false(),
327            totp_issuer: default_totp_issuer(),
328            totp_step: default_totp_step(),
329            totp_digits: default_totp_digits(),
330            totp_window: default_totp_window(),
331            backup_codes_count: default_backup_codes_count(),
332            backup_code_length: default_backup_code_length(),
333        }
334    }
335}
336
337impl Default for AuthRateLimitConfig {
338    fn default() -> Self {
339        Self {
340            max_attempts: default_max_attempts(),
341            window_seconds: default_rate_limit_window(),
342            lockout_duration: default_lockout_duration(),
343        }
344    }
345}
346
347impl AuthConfig {
348    /// Create a development configuration with secure defaults
349    pub fn development() -> Self {
350        let mut config = Self::default();
351        config.jwt.secret = "dev-secret-key-change-in-production".to_string();
352        config.session.cookie_secure = false; // Allow HTTP in development
353        config.password.require_special = false; // Relaxed for dev
354        config
355    }
356
357    /// Create a production configuration with strict security
358    pub fn production() -> Self {
359        let mut config = Self::default();
360        config.session.cookie_secure = true;
361        config.session.cookie_same_site = "Strict".to_string();
362        config.password.require_special = true;
363        config.password.min_length = 12;
364        config.mfa.enabled = true;
365        config
366    }
367
368    /// Validate the configuration
369    pub fn validate(&self) -> Result<(), String> {
370        // Validate JWT configuration
371        if self.jwt.secret.len() < 32 {
372            return Err("JWT secret must be at least 32 characters".to_string());
373        }
374
375        if !["HS256", "HS384", "HS512", "RS256", "RS384", "RS512"]
376            .contains(&self.jwt.algorithm.as_str())
377        {
378            return Err("Invalid JWT algorithm".to_string());
379        }
380
381        // Validate password policy
382        if self.password.min_length > self.password.max_length {
383            return Err("Password min_length cannot be greater than max_length".to_string());
384        }
385
386        if self.password.min_length < 1 {
387            return Err("Password min_length must be at least 1".to_string());
388        }
389
390        // Validate session configuration
391        if !["memory", "database", "redis"].contains(&self.session.storage.as_str()) {
392            return Err("Invalid session storage backend".to_string());
393        }
394
395        if !["Strict", "Lax", "None"].contains(&self.session.cookie_same_site.as_str()) {
396            return Err("Invalid session cookie SameSite policy".to_string());
397        }
398
399        Ok(())
400    }
401}
402
403#[cfg(test)]
404mod tests {
405    use super::*;
406
407    #[test]
408    fn test_default_config() {
409        let config = AuthConfig::default();
410        assert_eq!(config.jwt.algorithm, "HS256");
411        assert_eq!(config.session.storage, "memory");
412        assert_eq!(config.password.hash_algorithm, "argon2");
413        assert!(!config.mfa.enabled);
414    }
415
416    #[test]
417    fn test_development_config() {
418        let config = AuthConfig::development();
419        assert!(!config.session.cookie_secure);
420        assert!(!config.password.require_special);
421        assert_eq!(config.jwt.secret, "dev-secret-key-change-in-production");
422    }
423
424    #[test]
425    fn test_production_config() {
426        let config = AuthConfig::production();
427        assert!(config.session.cookie_secure);
428        assert!(config.password.require_special);
429        assert_eq!(config.password.min_length, 12);
430        assert!(config.mfa.enabled);
431        assert_eq!(config.session.cookie_same_site, "Strict");
432    }
433
434    #[test]
435    fn test_config_validation() {
436        let mut config = AuthConfig::default();
437        assert!(config.validate().is_ok());
438
439        // Test invalid JWT secret
440        config.jwt.secret = "short".to_string();
441        assert!(config.validate().is_err());
442
443        // Test invalid JWT algorithm
444        config.jwt.secret = "long-enough-secret-key-for-validation".to_string();
445        config.jwt.algorithm = "INVALID".to_string();
446        assert!(config.validate().is_err());
447
448        // Test invalid password policy
449        config.jwt.algorithm = "HS256".to_string();
450        config.password.min_length = 20;
451        config.password.max_length = 10;
452        assert!(config.validate().is_err());
453    }
454
455    #[test]
456    fn test_durations() {
457        let config = AuthConfig::default();
458        assert_eq!(config.jwt.access_token_expiry, 15 * 60); // 15 minutes
459        assert_eq!(config.jwt.refresh_token_expiry, 7 * 24 * 60 * 60); // 7 days
460        assert_eq!(config.session.expiry, 24 * 60 * 60); // 24 hours
461    }
462}