1pub mod app_config;
4pub mod config_manager;
5
6pub 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#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct AuthConfig {
20 pub token_lifetime: Duration,
22
23 pub refresh_token_lifetime: Duration,
25
26 pub enable_multi_factor: bool,
28
29 pub issuer: String,
31
32 pub audience: String,
34
35 pub secret: Option<String>,
37
38 pub storage: StorageConfig,
40
41 pub rate_limiting: RateLimitConfig,
43
44 pub security: SecurityConfig,
46
47 pub audit: AuditConfig,
49
50 pub method_configs: HashMap<String, serde_json::Value>,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
56pub enum StorageConfig {
57 Memory,
59
60 #[cfg(feature = "redis-storage")]
62 Redis { url: String, key_prefix: String },
63
64 #[cfg(feature = "postgres-storage")]
66 Postgres {
67 connection_string: String,
68 table_prefix: String,
69 },
70
71 #[cfg(feature = "mysql-storage")]
73 MySQL {
74 connection_string: String,
75 table_prefix: String,
76 },
77
78 Custom(String),
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct RateLimitConfig {
85 pub enabled: bool,
87
88 pub max_requests: u32,
90
91 pub window: Duration,
93
94 pub burst: u32,
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct SecurityConfig {
101 pub min_password_length: usize,
103
104 pub require_password_complexity: bool,
106
107 pub password_hash_algorithm: PasswordHashAlgorithm,
109
110 pub jwt_algorithm: JwtAlgorithm,
112
113 pub secret_key: Option<String>,
115
116 pub secure_cookies: bool,
118
119 pub cookie_same_site: CookieSameSite,
121
122 pub csrf_protection: bool,
124
125 pub session_timeout: Duration,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
131pub enum PasswordHashAlgorithm {
132 Argon2,
133 Bcrypt,
134 Scrypt,
135}
136
137#[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#[derive(Debug, Clone, Serialize, Deserialize)]
152pub enum CookieSameSite {
153 Strict,
154 Lax,
155 None,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct AuditConfig {
161 pub enabled: bool,
163
164 pub log_success: bool,
166
167 pub log_failures: bool,
169
170 pub log_permissions: bool,
172
173 pub log_tokens: bool,
175
176 pub storage: AuditStorage,
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
182pub enum AuditStorage {
183 Tracing,
185
186 File { path: String },
188
189 Database { connection_string: String },
191
192 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), refresh_token_lifetime: Duration::from_secs(86400 * 7), 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), 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), }
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, storage: AuditStorage::Tracing,
250 }
251 }
252}
253
254impl AuthConfig {
255 pub fn new() -> Self {
257 Self::default()
258 }
259
260 pub fn token_lifetime(mut self, lifetime: Duration) -> Self {
262 self.token_lifetime = lifetime;
263 self
264 }
265
266 pub fn refresh_token_lifetime(mut self, lifetime: Duration) -> Self {
268 self.refresh_token_lifetime = lifetime;
269 self
270 }
271
272 pub fn enable_multi_factor(mut self, enabled: bool) -> Self {
274 self.enable_multi_factor = enabled;
275 self
276 }
277
278 pub fn issuer(mut self, issuer: impl Into<String>) -> Self {
280 self.issuer = issuer.into();
281 self
282 }
283
284 pub fn audience(mut self, audience: impl Into<String>) -> Self {
286 self.audience = audience.into();
287 self
288 }
289
290 pub fn secret(mut self, secret: impl Into<String>) -> Self {
292 self.secret = Some(secret.into());
293 self
294 }
295
296 pub fn require_mfa(mut self, required: bool) -> Self {
298 self.enable_multi_factor = required;
299 self
300 }
301
302 pub fn enable_caching(self, _enabled: bool) -> Self {
304 self
306 }
307
308 pub fn max_failed_attempts(self, _max: u32) -> Self {
310 self
312 }
313
314 pub fn enable_rbac(self, _enabled: bool) -> Self {
316 self
318 }
319
320 pub fn enable_security_audit(self, _enabled: bool) -> Self {
322 self
324 }
325
326 pub fn enable_middleware(self, _enabled: bool) -> Self {
328 self
330 }
331
332 pub fn storage(mut self, storage: StorageConfig) -> Self {
334 self.storage = storage;
335 self
336 }
337
338 #[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 pub fn rate_limiting(mut self, config: RateLimitConfig) -> Self {
350 self.rate_limiting = config;
351 self
352 }
353
354 pub fn security(mut self, config: SecurityConfig) -> Self {
356 self.security = config;
357 self
358 }
359
360 pub fn audit(mut self, config: AuditConfig) -> Self {
362 self.audit = config;
363 self
364 }
365
366 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 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 pub fn validate(&self) -> Result<()> {
396 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 self.validate_jwt_secret()?;
415
416 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 if self.is_production_environment() && !self.is_test_environment() {
425 self.validate_production_security()?;
426 }
427
428 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 self.validate_storage_config()?;
437
438 Ok(())
439 }
440
441 fn validate_jwt_secret(&self) -> Result<()> {
443 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 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 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 fn validate_production_security(&self) -> Result<()> {
495 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 if !self.security.secure_cookies {
508 return Err(AuthError::config(
509 "Production environments must use secure cookies (HTTPS required)",
510 ));
511 }
512
513 if !self.rate_limiting.enabled {
515 tracing::warn!("Production deployment should enable rate limiting for security");
516 }
517
518 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 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 _ => {} }
548
549 Ok(())
550 }
551
552 fn is_production_environment(&self) -> bool {
554 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 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 fn is_test_environment(&self) -> bool {
593 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 pub fn new(max_requests: u32, window: Duration) -> Self {
608 Self {
609 enabled: true,
610 max_requests,
611 window,
612 burst: max_requests / 10, }
614 }
615
616 pub fn disabled() -> Self {
618 Self {
619 enabled: false,
620 ..Default::default()
621 }
622 }
623}
624
625impl SecurityConfig {
626 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), }
639 }
640
641 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, secure_cookies: false,
652 cookie_same_site: CookieSameSite::Lax,
653 csrf_protection: false,
654 session_timeout: Duration::from_secs(3600 * 24), }
656 }
657}