auth-framework 0.4.2

A comprehensive, production-ready authentication and authorization framework for Rust applications
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
//! Configuration types for the authentication framework.

pub mod app_config;
pub mod config_manager;

// Re-export for easy access
pub use config_manager::{
    AuthFrameworkSettings, ConfigBuilder, ConfigIntegration, ConfigManager, SessionCookieSettings,
    SessionSettings,
};

use crate::errors::{AuthError, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::Duration;

/// Main configuration for the authentication framework.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthConfig {
    /// Default token lifetime
    pub token_lifetime: Duration,

    /// Refresh token lifetime
    pub refresh_token_lifetime: Duration,

    /// Whether multi-factor authentication is enabled
    pub enable_multi_factor: bool,

    /// JWT issuer for token validation
    pub issuer: String,

    /// JWT audience for token validation
    pub audience: String,

    /// JWT secret key (optional - can be set via environment)
    pub secret: Option<String>,

    /// Storage configuration
    pub storage: StorageConfig,

    /// Rate limiting configuration
    pub rate_limiting: RateLimitConfig,

    /// Security configuration
    pub security: SecurityConfig,

    /// Audit logging configuration
    pub audit: AuditConfig,

    /// Custom settings for different auth methods
    pub method_configs: HashMap<String, serde_json::Value>,
}

/// Storage configuration options.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum StorageConfig {
    /// In-memory storage (not recommended for production)
    Memory,

    /// Redis storage
    #[cfg(feature = "redis-storage")]
    Redis { url: String, key_prefix: String },

    /// PostgreSQL storage
    #[cfg(feature = "postgres-storage")]
    Postgres {
        connection_string: String,
        table_prefix: String,
    },

    /// MySQL storage
    #[cfg(feature = "mysql-storage")]
    MySQL {
        connection_string: String,
        table_prefix: String,
    },

    /// Custom storage backend
    Custom(String),
}

/// Rate limiting configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RateLimitConfig {
    /// Enable rate limiting
    pub enabled: bool,

    /// Maximum requests per window
    pub max_requests: u32,

    /// Time window for rate limiting
    pub window: Duration,

    /// Burst allowance
    pub burst: u32,
}

/// Security configuration options.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityConfig {
    /// Minimum password length
    pub min_password_length: usize,

    /// Require password complexity
    pub require_password_complexity: bool,

    /// Password hash algorithm
    pub password_hash_algorithm: PasswordHashAlgorithm,

    /// JWT signing algorithm
    pub jwt_algorithm: JwtAlgorithm,

    /// Secret key for signing (should be loaded from environment)
    pub secret_key: Option<String>,

    /// Enable secure cookies
    pub secure_cookies: bool,

    /// Cookie SameSite policy
    pub cookie_same_site: CookieSameSite,

    /// CSRF protection
    pub csrf_protection: bool,

    /// Session timeout
    pub session_timeout: Duration,
}

/// Password hashing algorithms.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PasswordHashAlgorithm {
    Argon2,
    Bcrypt,
    Scrypt,
}

/// JWT signing algorithms.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum JwtAlgorithm {
    HS256,
    HS384,
    HS512,
    RS256,
    RS384,
    RS512,
    ES256,
    ES384,
}

/// Cookie SameSite policies.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CookieSameSite {
    Strict,
    Lax,
    None,
}

/// Audit logging configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditConfig {
    /// Enable audit logging
    pub enabled: bool,

    /// Log successful authentications
    pub log_success: bool,

    /// Log failed authentications
    pub log_failures: bool,

    /// Log permission checks
    pub log_permissions: bool,

    /// Log token operations
    pub log_tokens: bool,

    /// Audit log storage
    pub storage: AuditStorage,
}

/// Audit log storage options.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AuditStorage {
    /// Standard logging (via tracing)
    Tracing,

    /// File-based storage
    File { path: String },

    /// Database storage
    Database { connection_string: String },

    /// External service
    External { endpoint: String, api_key: String },
}

impl Default for AuthConfig {
    fn default() -> Self {
        Self {
            token_lifetime: Duration::from_secs(3600), // 1 hour
            refresh_token_lifetime: Duration::from_secs(86400 * 7), // 7 days
            enable_multi_factor: false,
            issuer: "auth-framework".to_string(),
            audience: "api".to_string(),
            secret: None,
            storage: StorageConfig::Memory,
            rate_limiting: RateLimitConfig::default(),
            security: SecurityConfig::default(),
            audit: AuditConfig::default(),
            method_configs: HashMap::new(),
        }
    }
}

impl Default for RateLimitConfig {
    fn default() -> Self {
        Self {
            enabled: true,
            max_requests: 100,
            window: Duration::from_secs(60), // 1 minute
            burst: 10,
        }
    }
}

impl Default for SecurityConfig {
    fn default() -> Self {
        Self {
            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: Duration::from_secs(3600 * 24), // 24 hours
        }
    }
}

impl Default for AuditConfig {
    fn default() -> Self {
        Self {
            enabled: true,
            log_success: true,
            log_failures: true,
            log_permissions: true,
            log_tokens: false, // Tokens can be sensitive
            storage: AuditStorage::Tracing,
        }
    }
}

impl AuthConfig {
    /// Create a new configuration with default values.
    pub fn new() -> Self {
        Self::default()
    }

    /// Set the token lifetime.
    pub fn token_lifetime(mut self, lifetime: Duration) -> Self {
        self.token_lifetime = lifetime;
        self
    }

    /// Set the refresh token lifetime.
    pub fn refresh_token_lifetime(mut self, lifetime: Duration) -> Self {
        self.refresh_token_lifetime = lifetime;
        self
    }

    /// Enable or disable multi-factor authentication.
    pub fn enable_multi_factor(mut self, enabled: bool) -> Self {
        self.enable_multi_factor = enabled;
        self
    }

    /// Set the JWT issuer.
    pub fn issuer(mut self, issuer: impl Into<String>) -> Self {
        self.issuer = issuer.into();
        self
    }

    /// Set the JWT audience.
    pub fn audience(mut self, audience: impl Into<String>) -> Self {
        self.audience = audience.into();
        self
    }

    /// Set the JWT secret key.
    pub fn secret(mut self, secret: impl Into<String>) -> Self {
        self.secret = Some(secret.into());
        self
    }

    /// Require MFA for all users.
    pub fn require_mfa(mut self, required: bool) -> Self {
        self.enable_multi_factor = required;
        self
    }

    /// Enable caching.
    pub fn enable_caching(self, _enabled: bool) -> Self {
        // This would set a caching flag in a real implementation
        self
    }

    /// Set maximum failed attempts.
    pub fn max_failed_attempts(self, _max: u32) -> Self {
        // This would set max failed attempts in security config
        self
    }

    /// Enable RBAC.
    pub fn enable_rbac(self, _enabled: bool) -> Self {
        // This would enable role-based access control
        self
    }

    /// Enable security audit.
    pub fn enable_security_audit(self, _enabled: bool) -> Self {
        // This would enable security auditing
        self
    }

    /// Enable middleware.
    pub fn enable_middleware(self, _enabled: bool) -> Self {
        // This would enable middleware support
        self
    }

    /// Set the storage configuration.
    pub fn storage(mut self, storage: StorageConfig) -> Self {
        self.storage = storage;
        self
    }

    /// Configure Redis storage.
    #[cfg(feature = "redis-storage")]
    pub fn redis_storage(mut self, url: impl Into<String>) -> Self {
        self.storage = StorageConfig::Redis {
            url: url.into(),
            key_prefix: "auth:".to_string(),
        };
        self
    }

    /// Set rate limiting configuration.
    pub fn rate_limiting(mut self, config: RateLimitConfig) -> Self {
        self.rate_limiting = config;
        self
    }

    /// Set security configuration.
    pub fn security(mut self, config: SecurityConfig) -> Self {
        self.security = config;
        self
    }

    /// Set audit configuration.
    pub fn audit(mut self, config: AuditConfig) -> Self {
        self.audit = config;
        self
    }

    /// Add configuration for a specific auth method.
    pub fn method_config(
        mut self,
        method_name: impl Into<String>,
        config: impl Serialize,
    ) -> Result<Self> {
        let value = serde_json::to_value(config)
            .map_err(|e| AuthError::config(format!("Failed to serialize method config: {e}")))?;

        self.method_configs.insert(method_name.into(), value);
        Ok(self)
    }

    /// Get configuration for a specific auth method.
    pub fn get_method_config<T>(&self, method_name: &str) -> Result<Option<T>>
    where
        T: for<'de> Deserialize<'de>,
    {
        if let Some(value) = self.method_configs.get(method_name) {
            let config = serde_json::from_value(value.clone()).map_err(|e| {
                AuthError::config(format!("Failed to deserialize method config: {e}"))
            })?;
            Ok(Some(config))
        } else {
            Ok(None)
        }
    }

    /// Validate the configuration.
    pub fn validate(&self) -> Result<()> {
        // Validate token lifetimes
        if self.token_lifetime.as_secs() == 0 {
            return Err(AuthError::config("Token lifetime must be greater than 0"));
        }

        if self.refresh_token_lifetime.as_secs() == 0 {
            return Err(AuthError::config(
                "Refresh token lifetime must be greater than 0",
            ));
        }

        if self.refresh_token_lifetime <= self.token_lifetime {
            return Err(AuthError::config(
                "Refresh token lifetime must be greater than token lifetime",
            ));
        }

        // Validate JWT secret configuration
        self.validate_jwt_secret()?;

        // Validate security settings
        if self.security.min_password_length < 4 {
            return Err(AuthError::config(
                "Minimum password length must be at least 4 characters",
            ));
        }

        // Enhanced security validation for production
        if self.is_production_environment() && !self.is_test_environment() {
            self.validate_production_security()?;
        }

        // Validate rate limiting
        if self.rate_limiting.enabled && self.rate_limiting.max_requests == 0 {
            return Err(AuthError::config(
                "Rate limit max requests must be greater than 0 when enabled",
            ));
        }

        // Validate storage configuration
        self.validate_storage_config()?;

        Ok(())
    }

    /// Validate JWT secret configuration for security
    fn validate_jwt_secret(&self) -> Result<()> {
        // Check multiple sources for JWT secret
        let env_secret = std::env::var("JWT_SECRET").ok();
        let jwt_secret = self
            .security
            .secret_key
            .as_ref()
            .or(self.secret.as_ref())
            .or(env_secret.as_ref());

        if let Some(secret) = jwt_secret {
            if secret.len() < 32 {
                return Err(AuthError::config(
                    "JWT secret must be at least 32 characters for security. \
                     Generate with: openssl rand -base64 32",
                ));
            }

            // Check for common insecure patterns (but allow in test environments)
            if !self.is_test_environment()
                && (secret.contains("secret")
                    || secret.contains("password")
                    || secret.contains("123"))
            {
                return Err(AuthError::config(
                    "JWT secret appears to contain common words or patterns. \
                     Use a cryptographically secure random string.",
                ));
            }

            // Warn if secret looks like it might be base64 but too short
            if secret.len() < 44
                && secret
                    .chars()
                    .all(|c| c.is_alphanumeric() || c == '+' || c == '/' || c == '=')
            {
                tracing::warn!(
                    "JWT secret may be too short for optimal security. \
                     Consider using at least 44 characters (32 bytes base64-encoded)."
                );
            }
        } else if self.is_production_environment() {
            return Err(AuthError::config(
                "JWT secret is required for production environments. \
                 Set JWT_SECRET environment variable or configure security.secret_key",
            ));
        }

        Ok(())
    }

    /// Validate production-specific security requirements
    fn validate_production_security(&self) -> Result<()> {
        // Require strong password policies in production
        if self.security.min_password_length < 8 {
            return Err(AuthError::config(
                "Production environments require minimum password length of 8 characters",
            ));
        }

        if !self.security.require_password_complexity {
            tracing::warn!("Production deployment should enable password complexity requirements");
        }

        // Require secure cookies in production
        if !self.security.secure_cookies {
            return Err(AuthError::config(
                "Production environments must use secure cookies (HTTPS required)",
            ));
        }

        // Ensure rate limiting is enabled
        if !self.rate_limiting.enabled {
            tracing::warn!("Production deployment should enable rate limiting for security");
        }

        // Validate audit configuration for compliance
        if !self.audit.enabled {
            return Err(AuthError::config(
                "Production environments require audit logging for compliance",
            ));
        }

        Ok(())
    }

    /// Validate storage configuration
    fn validate_storage_config(&self) -> Result<()> {
        match &self.storage {
            StorageConfig::Memory => {
                if self.is_production_environment() && !self.is_test_environment() {
                    return Err(AuthError::config(
                        "Memory storage is not suitable for production environments. \
                         Use PostgreSQL, Redis, or MySQL storage.",
                    ));
                }
            }
            #[cfg(feature = "mysql-storage")]
            StorageConfig::MySQL { .. } => {
                tracing::warn!(
                    "MySQL storage has known RSA vulnerability (RUSTSEC-2023-0071). \
                     Consider using PostgreSQL for enhanced security."
                );
            }
            _ => {} // PostgreSQL and Redis are production-ready
        }

        Ok(())
    }

    /// Detect production environment
    fn is_production_environment(&self) -> bool {
        // Check common production environment indicators
        if let Ok(env) = std::env::var("ENVIRONMENT")
            && (env.to_lowercase() == "production" || env.to_lowercase() == "prod")
        {
            return true;
        }

        if let Ok(env) = std::env::var("ENV")
            && (env.to_lowercase() == "production" || env.to_lowercase() == "prod")
        {
            return true;
        }

        if let Ok(env) = std::env::var("NODE_ENV")
            && env.to_lowercase() == "production"
        {
            return true;
        }

        if let Ok(env) = std::env::var("RUST_ENV")
            && env.to_lowercase() == "production"
        {
            return true;
        }

        // Check for containerized environments
        if std::env::var("KUBERNETES_SERVICE_HOST").is_ok() {
            return true;
        }

        if std::env::var("DOCKER_CONTAINER").is_ok() {
            return true;
        }

        false
    }

    /// Detect test environment
    fn is_test_environment(&self) -> bool {
        // Check if we're running in a test environment
        cfg!(test)
            || std::thread::current()
                .name()
                .is_some_and(|name| name.contains("test"))
            || std::env::var("RUST_TEST").is_ok()
            || std::env::var("ENVIRONMENT").as_deref() == Ok("test")
            || std::env::var("ENV").as_deref() == Ok("test")
            || std::env::args().any(|arg| arg.contains("test"))
    }
}

impl RateLimitConfig {
    /// Create a new rate limit configuration.
    pub fn new(max_requests: u32, window: Duration) -> Self {
        Self {
            enabled: true,
            max_requests,
            window,
            burst: max_requests / 10, // 10% of max as burst
        }
    }

    /// Disable rate limiting.
    pub fn disabled() -> Self {
        Self {
            enabled: false,
            ..Default::default()
        }
    }
}

impl SecurityConfig {
    /// Create a new security configuration with secure defaults.
    pub fn secure() -> Self {
        Self {
            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: Duration::from_secs(3600 * 8), // 8 hours
        }
    }

    /// Create a development-friendly configuration.
    /// WARNING: You MUST set a secret key before using this configuration!
    /// Use either config.security.secret_key or JWT_SECRET environment variable.
    pub fn development() -> Self {
        Self {
            min_password_length: 6,
            require_password_complexity: false,
            password_hash_algorithm: PasswordHashAlgorithm::Bcrypt,
            jwt_algorithm: JwtAlgorithm::HS256,
            secret_key: None, // Must be set by developer for security
            secure_cookies: false,
            cookie_same_site: CookieSameSite::Lax,
            csrf_protection: false,
            session_timeout: Duration::from_secs(3600 * 24), // 24 hours
        }
    }
}