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 pub per_user_enabled: bool,
99
100 pub max_requests_per_user: u32,
102
103 pub per_user_window: Duration,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct SecurityConfig {
110 pub min_password_length: usize,
112
113 pub require_password_complexity: bool,
115
116 pub require_uppercase: bool,
118
119 pub require_lowercase: bool,
121
122 pub require_digit: bool,
124
125 pub require_special: bool,
127
128 pub min_complexity_criteria: usize,
130
131 pub password_hash_algorithm: PasswordHashAlgorithm,
133
134 pub jwt_algorithm: JwtAlgorithm,
136
137 pub secret_key: Option<String>,
139
140 pub secure_cookies: bool,
142
143 pub cookie_same_site: CookieSameSite,
145
146 pub csrf_protection: bool,
148
149 pub session_timeout: Duration,
151
152 pub lockout: LockoutConfig,
154
155 pub max_api_keys_per_user: usize,
157
158 pub oauth2: OAuth2SecurityConfig,
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
164pub enum PasswordHashAlgorithm {
165 Argon2,
166 Bcrypt,
167 Scrypt,
168}
169
170#[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#[derive(Debug, Clone, Serialize, Deserialize)]
185pub enum CookieSameSite {
186 Strict,
187 Lax,
188 None,
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct LockoutConfig {
194 pub enabled: bool,
196
197 pub max_failed_attempts: u32,
199
200 pub lockout_duration_seconds: u64,
202
203 pub progressive_lockout: bool,
205
206 pub max_lockout_duration_seconds: u64,
208
209 pub tracking_window_seconds: u64,
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize)]
215pub struct OAuth2SecurityConfig {
216 pub require_user_authentication: bool,
218
219 pub validate_redirect_uri: bool,
221
222 pub require_client_secret: bool,
224
225 pub require_pkce: bool,
227}
228
229#[derive(Debug, Clone, Serialize, Deserialize)]
231pub struct OAuth2Client {
232 pub client_id: String,
234
235 pub client_secret_hash: Option<String>,
237
238 pub name: String,
240
241 pub redirect_uris: Vec<String>,
243
244 pub grant_types: Vec<String>,
246
247 pub scopes: Vec<String>,
249
250 pub active: bool,
252
253 pub created_at: String,
255
256 pub updated_at: String,
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
262pub struct AuditConfig {
263 pub enabled: bool,
265
266 pub log_success: bool,
268
269 pub log_failures: bool,
271
272 pub log_permissions: bool,
274
275 pub log_tokens: bool,
277
278 pub storage: AuditStorage,
280}
281
282#[derive(Debug, Clone, Serialize, Deserialize)]
284pub enum AuditStorage {
285 Tracing,
287
288 File { path: String },
290
291 Database { connection_string: String },
293
294 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), refresh_token_lifetime: Duration::from_secs(86400 * 7), 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), burst: 10,
323 per_user_enabled: true,
324 max_requests_per_user: 120, 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, 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, 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), lockout: LockoutConfig::default(),
348 max_api_keys_per_user: 10, 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, 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, progressive_lockout: true,
374 max_lockout_duration_seconds: 3600, tracking_window_seconds: 300, }
377 }
378}
379
380impl Default for OAuth2SecurityConfig {
381 fn default() -> Self {
382 Self {
383 require_user_authentication: true, validate_redirect_uri: true, require_client_secret: true, require_pkce: true, }
388 }
389}
390
391impl AuthConfig {
392 pub fn new() -> Self {
394 Self::default()
395 }
396
397 pub fn token_lifetime(mut self, lifetime: Duration) -> Self {
399 self.token_lifetime = lifetime;
400 self
401 }
402
403 pub fn refresh_token_lifetime(mut self, lifetime: Duration) -> Self {
405 self.refresh_token_lifetime = lifetime;
406 self
407 }
408
409 pub fn enable_multi_factor(mut self, enabled: bool) -> Self {
411 self.enable_multi_factor = enabled;
412 self
413 }
414
415 pub fn issuer(mut self, issuer: impl Into<String>) -> Self {
417 self.issuer = issuer.into();
418 self
419 }
420
421 pub fn audience(mut self, audience: impl Into<String>) -> Self {
423 self.audience = audience.into();
424 self
425 }
426
427 pub fn secret(mut self, secret: impl Into<String>) -> Self {
429 self.secret = Some(secret.into());
430 self
431 }
432
433 pub fn require_mfa(mut self, required: bool) -> Self {
435 self.enable_multi_factor = required;
436 self
437 }
438
439 pub fn enable_caching(self, _enabled: bool) -> Self {
441 self
443 }
444
445 pub fn max_failed_attempts(self, _max: u32) -> Self {
447 self
449 }
450
451 pub fn enable_rbac(self, _enabled: bool) -> Self {
453 self
455 }
456
457 pub fn enable_security_audit(self, _enabled: bool) -> Self {
459 self
461 }
462
463 pub fn enable_middleware(self, _enabled: bool) -> Self {
465 self
467 }
468
469 pub fn storage(mut self, storage: StorageConfig) -> Self {
471 self.storage = storage;
472 self
473 }
474
475 #[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 pub fn rate_limiting(mut self, config: RateLimitConfig) -> Self {
487 self.rate_limiting = config;
488 self
489 }
490
491 pub fn security(mut self, config: SecurityConfig) -> Self {
493 self.security = config;
494 self
495 }
496
497 pub fn audit(mut self, config: AuditConfig) -> Self {
499 self.audit = config;
500 self
501 }
502
503 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 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 pub fn validate(&self) -> Result<()> {
533 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 self.validate_jwt_secret()?;
552
553 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 if self.is_production_environment() && !self.is_test_environment() {
562 self.validate_production_security()?;
563 }
564
565 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 self.validate_storage_config()?;
574
575 Ok(())
576 }
577
578 fn validate_jwt_secret(&self) -> Result<()> {
580 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 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 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 fn validate_production_security(&self) -> Result<()> {
632 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 if !self.security.secure_cookies {
645 return Err(AuthError::config(
646 "Production environments must use secure cookies (HTTPS required)",
647 ));
648 }
649
650 if !self.rate_limiting.enabled {
652 tracing::warn!("Production deployment should enable rate limiting for security");
653 }
654
655 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 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 _ => {} }
685
686 Ok(())
687 }
688
689 fn is_production_environment(&self) -> bool {
691 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 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 fn is_test_environment(&self) -> bool {
730 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 pub fn new(max_requests: u32, window: Duration) -> Self {
745 Self {
746 enabled: true,
747 max_requests,
748 window,
749 burst: max_requests / 10, per_user_enabled: true,
751 max_requests_per_user: max_requests + 20,
752 per_user_window: window,
753 }
754 }
755
756 pub fn disabled() -> Self {
758 Self {
759 enabled: false,
760 per_user_enabled: false,
761 ..Default::default()
762 }
763 }
764}
765
766impl SecurityConfig {
767 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, 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), lockout: LockoutConfig::default(),
785 max_api_keys_per_user: 5, oauth2: OAuth2SecurityConfig::default(),
787 }
788 }
789
790 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, password_hash_algorithm: PasswordHashAlgorithm::Bcrypt,
803 jwt_algorithm: JwtAlgorithm::HS256,
804 secret_key: None, secure_cookies: false,
806 cookie_same_site: CookieSameSite::Lax,
807 csrf_protection: false,
808 session_timeout: Duration::from_secs(3600 * 24), lockout: LockoutConfig {
810 enabled: false, ..Default::default()
812 },
813 max_api_keys_per_user: 0, oauth2: OAuth2SecurityConfig {
815 require_user_authentication: false, validate_redirect_uri: false,
817 require_client_secret: false,
818 require_pkce: false,
819 },
820 }
821 }
822}