1pub mod app_config;
4pub mod config_manager;
5
6pub use app_config::{AppConfig, ConfigBuilder as AppConfigBuilder};
8pub use config_manager::{
9 AuthFrameworkSettings, ConfigBuilder as LayeredConfigBuilder, ConfigIntegration, ConfigManager,
10 SessionCookieSettings, SessionSettings,
11};
12
13use crate::errors::{AuthError, Result};
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16use std::time::Duration;
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct AuthConfig {
21 pub token_lifetime: Duration,
23
24 pub refresh_token_lifetime: Duration,
26
27 pub enable_multi_factor: bool,
29
30 pub issuer: String,
32
33 pub audience: String,
35
36 pub secret: Option<String>,
38
39 pub storage: StorageConfig,
41
42 pub rate_limiting: RateLimitConfig,
44
45 pub security: SecurityConfig,
47
48 #[serde(default)]
50 pub cors: CorsConfig,
51
52 pub audit: AuditConfig,
54
55 #[serde(default)]
57 pub enable_caching: bool,
58
59 #[serde(default = "default_max_failed_attempts")]
61 pub max_failed_attempts: u32,
62
63 #[serde(default)]
65 pub enable_rbac: bool,
66
67 #[serde(default)]
69 pub enable_middleware: bool,
70
71 pub method_configs: HashMap<String, serde_json::Value>,
73
74 #[serde(default)]
77 pub force_production_mode: bool,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub enum StorageConfig {
83 Memory,
85
86 #[cfg(feature = "redis-storage")]
88 Redis { url: String, key_prefix: String },
89
90 #[cfg(feature = "postgres-storage")]
92 Postgres {
93 connection_string: String,
94 table_prefix: String,
95 },
96
97 #[cfg(feature = "mysql-storage")]
99 MySQL {
100 connection_string: String,
101 table_prefix: String,
102 },
103
104 #[cfg(feature = "sqlite-storage")]
106 Sqlite { connection_string: String },
107
108 Custom(String),
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct RateLimitConfig {
119 pub enabled: bool,
121
122 pub max_requests: u32,
124
125 pub window: Duration,
127
128 pub burst: u32,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct SecurityConfig {
145 pub min_password_length: usize,
147
148 pub require_password_complexity: bool,
150
151 pub password_hash_algorithm: PasswordHashAlgorithm,
153
154 pub jwt_algorithm: JwtAlgorithm,
156
157 pub secret_key: Option<String>,
159
160 pub previous_secret_key: Option<String>,
162
163 pub secure_cookies: bool,
165
166 pub cookie_same_site: CookieSameSite,
168
169 pub csrf_protection: bool,
171
172 pub session_timeout: Duration,
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize)]
178pub enum PasswordHashAlgorithm {
179 Argon2,
181 Bcrypt,
183 Scrypt,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
189pub enum JwtAlgorithm {
190 HS256,
192 HS384,
194 HS512,
196 RS256,
198 RS384,
200 RS512,
202 ES256,
204 ES384,
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize)]
210pub enum CookieSameSite {
211 Strict,
213 Lax,
215 None,
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct CorsConfig {
226 pub enabled: bool,
228
229 pub allowed_origins: Vec<String>,
234
235 pub allowed_methods: Vec<String>,
237
238 pub allowed_headers: Vec<String>,
240
241 pub max_age_secs: u32,
243}
244
245impl Default for CorsConfig {
246 fn default() -> Self {
247 Self {
248 enabled: false,
249 allowed_origins: Vec::new(),
250 allowed_methods: vec![
251 "GET".to_string(),
252 "POST".to_string(),
253 "PUT".to_string(),
254 "DELETE".to_string(),
255 "OPTIONS".to_string(),
256 ],
257 allowed_headers: vec![
258 "Authorization".to_string(),
259 "Content-Type".to_string(),
260 "Accept".to_string(),
261 ],
262 max_age_secs: 3600,
263 }
264 }
265}
266
267impl CorsConfig {
268 pub fn for_origins<I, S>(origins: I) -> Self
279 where
280 I: IntoIterator<Item = S>,
281 S: Into<String>,
282 {
283 Self {
284 enabled: true,
285 allowed_origins: origins.into_iter().map(Into::into).collect(),
286 ..Default::default()
287 }
288 }
289}
290
291#[derive(Debug, Clone, Serialize, Deserialize)]
296pub struct AuditConfig {
297 pub enabled: bool,
299
300 pub log_success: bool,
302
303 pub log_failures: bool,
305
306 pub log_permissions: bool,
308
309 pub log_tokens: bool,
311
312 pub storage: AuditStorage,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
320pub enum AuditStorage {
321 Tracing,
323
324 File { path: String },
326
327 Database { connection_string: String },
329
330 External { endpoint: String, api_key: String },
332}
333
334const fn default_max_failed_attempts() -> u32 {
335 5
336}
337
338impl Default for AuthConfig {
339 fn default() -> Self {
340 Self {
341 token_lifetime: Duration::from_secs(3600), refresh_token_lifetime: Duration::from_secs(86400 * 7), enable_multi_factor: false,
344 issuer: "auth-framework".to_string(),
345 audience: "api".to_string(),
346 secret: None,
347 storage: StorageConfig::Memory,
348 rate_limiting: RateLimitConfig::default(),
349 security: SecurityConfig::default(),
350 cors: CorsConfig::default(),
351 audit: AuditConfig::default(),
352 enable_caching: false,
353 max_failed_attempts: default_max_failed_attempts(),
354 enable_rbac: false,
355 enable_middleware: false,
356 method_configs: HashMap::new(),
357 force_production_mode: false,
358 }
359 }
360}
361
362impl Default for RateLimitConfig {
363 fn default() -> Self {
364 Self {
365 enabled: true,
366 max_requests: 100,
367 window: Duration::from_secs(60), burst: 10,
369 }
370 }
371}
372
373impl Default for SecurityConfig {
374 fn default() -> Self {
375 Self {
376 min_password_length: 8,
377 require_password_complexity: true,
378 password_hash_algorithm: PasswordHashAlgorithm::Argon2,
379 jwt_algorithm: JwtAlgorithm::HS256,
380 secret_key: None,
381 previous_secret_key: None,
382 secure_cookies: true,
383 cookie_same_site: CookieSameSite::Lax,
384 csrf_protection: true,
385 session_timeout: Duration::from_secs(3600 * 24), }
387 }
388}
389
390impl Default for AuditConfig {
391 fn default() -> Self {
392 Self {
393 enabled: true,
394 log_success: true,
395 log_failures: true,
396 log_permissions: true,
397 log_tokens: false, storage: AuditStorage::Tracing,
399 }
400 }
401}
402
403impl std::fmt::Display for AuthConfig {
408 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
409 write!(
410 f,
411 "AuthConfig {{ tokens: {}s/{}s refresh, storage: {}, mfa: {}, rbac: {}, rate_limit: {}, security: {}, cors: {}, audit: {} }}",
412 self.token_lifetime.as_secs(),
413 self.refresh_token_lifetime.as_secs(),
414 self.storage,
415 if self.enable_multi_factor { "on" } else { "off" },
416 if self.enable_rbac { "on" } else { "off" },
417 self.rate_limiting,
418 self.security,
419 self.cors,
420 self.audit,
421 )
422 }
423}
424
425impl std::fmt::Display for StorageConfig {
426 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
427 match self {
428 Self::Memory => write!(f, "memory"),
429 #[cfg(feature = "redis-storage")]
430 Self::Redis { url, .. } => write!(f, "redis({})", url),
431 #[cfg(feature = "postgres-storage")]
432 Self::Postgres { .. } => write!(f, "postgres"),
433 #[cfg(feature = "mysql-storage")]
434 Self::MySQL { .. } => write!(f, "mysql"),
435 #[cfg(feature = "sqlite-storage")]
436 Self::Sqlite { .. } => write!(f, "sqlite"),
437 Self::Custom(name) => write!(f, "custom({})", name),
438 }
439 }
440}
441
442impl std::fmt::Display for SecurityConfig {
443 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
444 write!(
445 f,
446 "Security {{ pw≥{}, hash: {}, jwt: {}, cookies: {}, csrf: {}, session: {}s }}",
447 self.min_password_length,
448 self.password_hash_algorithm,
449 self.jwt_algorithm,
450 if self.secure_cookies { "secure" } else { "plain" },
451 if self.csrf_protection { "on" } else { "off" },
452 self.session_timeout.as_secs(),
453 )
454 }
455}
456
457impl std::fmt::Display for PasswordHashAlgorithm {
458 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
459 match self {
460 Self::Argon2 => write!(f, "argon2id"),
461 Self::Bcrypt => write!(f, "bcrypt"),
462 Self::Scrypt => write!(f, "scrypt"),
463 }
464 }
465}
466
467impl std::fmt::Display for JwtAlgorithm {
468 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
469 match self {
470 Self::HS256 => write!(f, "HS256"),
471 Self::HS384 => write!(f, "HS384"),
472 Self::HS512 => write!(f, "HS512"),
473 Self::RS256 => write!(f, "RS256"),
474 Self::RS384 => write!(f, "RS384"),
475 Self::RS512 => write!(f, "RS512"),
476 Self::ES256 => write!(f, "ES256"),
477 Self::ES384 => write!(f, "ES384"),
478 }
479 }
480}
481
482impl std::fmt::Display for RateLimitConfig {
483 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
484 if self.enabled {
485 write!(
486 f,
487 "{} req/{}s (burst {})",
488 self.max_requests,
489 self.window.as_secs(),
490 self.burst,
491 )
492 } else {
493 write!(f, "disabled")
494 }
495 }
496}
497
498impl std::fmt::Display for CorsConfig {
499 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
500 if self.enabled {
501 if self.allowed_origins.is_empty() {
502 write!(f, "cors(no origins)")
503 } else {
504 write!(f, "cors({})", self.allowed_origins.join(", "))
505 }
506 } else {
507 write!(f, "cors(off)")
508 }
509 }
510}
511
512impl std::fmt::Display for AuditConfig {
513 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
514 if self.enabled {
515 write!(f, "audit({})", self.storage)
516 } else {
517 write!(f, "audit(off)")
518 }
519 }
520}
521
522impl std::fmt::Display for AuditStorage {
523 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
524 match self {
525 Self::Tracing => write!(f, "tracing"),
526 Self::File { path } => write!(f, "file:{}", path),
527 Self::Database { .. } => write!(f, "database"),
528 Self::External { endpoint, .. } => write!(f, "external:{}", endpoint),
529 }
530 }
531}
532
533#[derive(Debug, Clone, Serialize, Deserialize)]
539pub struct RuntimeConfig {
540 pub token_lifetime_secs: u64,
542 pub refresh_token_lifetime_secs: u64,
544 pub enable_multi_factor: bool,
546 pub rate_limiting_enabled: bool,
548 pub rate_limit_max_requests: u32,
550 pub rate_limit_window_secs: u64,
552 pub rate_limit_burst: u32,
554 pub min_password_length: usize,
556 pub require_password_complexity: bool,
558 pub secure_cookies: bool,
560 pub csrf_protection: bool,
562 pub session_timeout_secs: u64,
564 pub audit_enabled: bool,
566 pub audit_log_success: bool,
568 pub audit_log_failures: bool,
570 pub audit_log_permissions: bool,
572 pub audit_log_tokens: bool,
574}
575
576impl RuntimeConfig {
577 pub fn from_auth_config(cfg: &AuthConfig) -> Self {
589 Self {
590 token_lifetime_secs: cfg.token_lifetime.as_secs(),
591 refresh_token_lifetime_secs: cfg.refresh_token_lifetime.as_secs(),
592 enable_multi_factor: cfg.enable_multi_factor,
593 rate_limiting_enabled: cfg.rate_limiting.enabled,
594 rate_limit_max_requests: cfg.rate_limiting.max_requests,
595 rate_limit_window_secs: cfg.rate_limiting.window.as_secs(),
596 rate_limit_burst: cfg.rate_limiting.burst,
597 min_password_length: cfg.security.min_password_length,
598 require_password_complexity: cfg.security.require_password_complexity,
599 secure_cookies: cfg.security.secure_cookies,
600 csrf_protection: cfg.security.csrf_protection,
601 session_timeout_secs: cfg.security.session_timeout.as_secs(),
602 audit_enabled: cfg.audit.enabled,
603 audit_log_success: cfg.audit.log_success,
604 audit_log_failures: cfg.audit.log_failures,
605 audit_log_permissions: cfg.audit.log_permissions,
606 audit_log_tokens: cfg.audit.log_tokens,
607 }
608 }
609}
610
611impl Default for RuntimeConfig {
612 fn default() -> Self {
613 Self::from_auth_config(&AuthConfig::default())
614 }
615}
616
617impl AuthConfig {
618 pub fn new() -> Self {
650 Self::default()
651 }
652
653 pub fn from_env() -> Self {
677 let mut config = Self::default();
678
679 if let Ok(secret) = std::env::var("JWT_SECRET") {
680 config.secret = Some(secret.clone());
681 config.security.secret_key = Some(secret);
682 }
683
684 if let Ok(issuer) = std::env::var("AUTH_ISSUER") {
685 config.issuer = issuer;
686 }
687
688 if let Ok(audience) = std::env::var("AUTH_AUDIENCE") {
689 config.audience = audience;
690 }
691
692 #[cfg(feature = "postgres-storage")]
694 if let Ok(url) = std::env::var("DATABASE_URL") {
695 config.storage = StorageConfig::Postgres {
696 connection_string: url,
697 table_prefix: "auth_".to_string(),
698 };
699 }
700
701 #[cfg(feature = "redis-storage")]
702 if matches!(config.storage, StorageConfig::Memory) {
703 if let Ok(url) = std::env::var("REDIS_URL") {
704 config.storage = StorageConfig::Redis {
705 url,
706 key_prefix: "auth:".to_string(),
707 };
708 }
709 }
710
711 config
712 }
713
714 pub fn builder() -> crate::builders::AuthBuilder {
736 crate::builders::AuthBuilder::new()
737 }
738
739 pub fn token_lifetime(mut self, lifetime: Duration) -> Self {
751 self.token_lifetime = lifetime;
752 self
753 }
754
755 pub fn refresh_token_lifetime(mut self, lifetime: Duration) -> Self {
767 self.refresh_token_lifetime = lifetime;
768 self
769 }
770
771 pub fn enable_multi_factor(mut self, enabled: bool) -> Self {
782 self.enable_multi_factor = enabled;
783 self
784 }
785
786 pub fn issuer(mut self, issuer: impl Into<String>) -> Self {
788 self.issuer = issuer.into();
789 self
790 }
791
792 pub fn audience(mut self, audience: impl Into<String>) -> Self {
794 self.audience = audience.into();
795 self
796 }
797
798 pub fn secret(mut self, secret: impl Into<String>) -> Self {
800 self.secret = Some(secret.into());
801 self
802 }
803
804 pub fn require_mfa(mut self, required: bool) -> Self {
815 self.enable_multi_factor = required;
816 self
817 }
818
819 pub fn enable_caching(mut self, enabled: bool) -> Self {
821 self.enable_caching = enabled;
822 self
823 }
824
825 pub fn max_failed_attempts(mut self, max: u32) -> Self {
836 self.max_failed_attempts = max;
837 self
838 }
839
840 pub fn enable_rbac(mut self, enabled: bool) -> Self {
851 self.enable_rbac = enabled;
852 self
853 }
854
855 pub fn enable_security_audit(mut self, enabled: bool) -> Self {
866 self.audit.enabled = enabled;
867 self
868 }
869
870 pub fn enable_middleware(mut self, enabled: bool) -> Self {
881 self.enable_middleware = enabled;
882 self
883 }
884
885 pub fn force_production_mode(mut self) -> Self {
898 self.force_production_mode = true;
899 self
900 }
901
902 pub fn storage(mut self, storage: StorageConfig) -> Self {
912 self.storage = storage;
913 self
914 }
915
916 #[cfg(feature = "redis-storage")]
926 pub fn redis_storage(mut self, url: impl Into<String>) -> Self {
927 self.storage = StorageConfig::Redis {
928 url: url.into(),
929 key_prefix: "auth:".to_string(),
930 };
931 self
932 }
933
934 pub fn rate_limiting(mut self, config: RateLimitConfig) -> Self {
944 self.rate_limiting = config;
945 self
946 }
947
948 pub fn security(mut self, config: SecurityConfig) -> Self {
958 self.security = config;
959 self
960 }
961
962 pub fn cors(mut self, config: CorsConfig) -> Self {
973 self.cors = config;
974 self
975 }
976
977 pub fn audit(mut self, config: AuditConfig) -> Self {
987 self.audit = config;
988 self
989 }
990
991 pub fn method_config(
1006 mut self,
1007 method_name: impl Into<String>,
1008 config: impl Serialize,
1009 ) -> Result<Self> {
1010 let value = serde_json::to_value(config)
1011 .map_err(|e| AuthError::config(format!("Failed to serialize method config: {e}")))?;
1012
1013 self.method_configs.insert(method_name.into(), value);
1014 Ok(self)
1015 }
1016
1017 pub fn get_method_config<T>(&self, method_name: &str) -> Result<Option<T>>
1029 where
1030 T: for<'de> Deserialize<'de>,
1031 {
1032 if let Some(value) = self.method_configs.get(method_name) {
1033 let config = serde_json::from_value(value.clone()).map_err(|e| {
1034 AuthError::config(format!("Failed to deserialize method config: {e}"))
1035 })?;
1036 Ok(Some(config))
1037 } else {
1038 Ok(None)
1039 }
1040 }
1041
1042 pub fn validate(&self) -> Result<()> {
1053 if self.token_lifetime.as_secs() == 0 {
1055 return Err(AuthError::config("Token lifetime must be greater than 0"));
1056 }
1057
1058 if self.refresh_token_lifetime.as_secs() == 0 {
1059 return Err(AuthError::config(
1060 "Refresh token lifetime must be greater than 0",
1061 ));
1062 }
1063
1064 if self.refresh_token_lifetime <= self.token_lifetime {
1065 return Err(AuthError::config(
1066 "Refresh token lifetime must be greater than token lifetime",
1067 ));
1068 }
1069
1070 self.validate_jwt_secret()?;
1072
1073 if self.security.min_password_length < 4 {
1075 return Err(AuthError::config(
1076 "Minimum password length must be at least 4 characters",
1077 ));
1078 }
1079
1080 if self.is_production_environment() && !self.is_test_environment() {
1082 self.validate_production_security()?;
1083 }
1084
1085 if self.rate_limiting.enabled && self.rate_limiting.max_requests == 0 {
1087 return Err(AuthError::config(
1088 "Rate limit max requests must be greater than 0 when enabled",
1089 ));
1090 }
1091
1092 self.validate_storage_config()?;
1094
1095 self.validate_method_configs()?;
1098
1099 Ok(())
1100 }
1101
1102 fn validate_method_configs(&self) -> Result<()> {
1103 for (method_name, raw_config) in &self.method_configs {
1104 match method_name.as_str() {
1105 #[cfg(feature = "saml")]
1106 "saml" => {
1107 let config: crate::methods::saml::SamlConfig =
1108 serde_json::from_value(raw_config.clone()).map_err(|e| {
1109 AuthError::config(format!(
1110 "Failed to deserialize SAML method config: {e}"
1111 ))
1112 })?;
1113
1114 if config.entity_id.trim().is_empty() {
1115 return Err(AuthError::config("SAML entity_id cannot be empty"));
1116 }
1117 if config.acs_url.trim().is_empty() {
1118 return Err(AuthError::config("SAML acs_url cannot be empty"));
1119 }
1120 if config.max_assertion_age == 0 {
1121 return Err(AuthError::config(
1122 "SAML max_assertion_age must be greater than 0",
1123 ));
1124 }
1125 }
1126 #[cfg(not(feature = "saml"))]
1127 "saml" => {
1128 return Err(AuthError::config(
1129 "SAML method config is present but the 'saml' feature is not enabled",
1130 ));
1131 }
1132 #[cfg(feature = "passkeys")]
1133 "passkey" => {
1134 let config: crate::methods::passkey::PasskeyConfig =
1135 serde_json::from_value(raw_config.clone()).map_err(|e| {
1136 AuthError::config(format!(
1137 "Failed to deserialize passkey method config: {e}"
1138 ))
1139 })?;
1140
1141 if config.rp_id.trim().is_empty() {
1142 return Err(AuthError::config("Passkey RP ID cannot be empty"));
1143 }
1144 if config.origin.trim().is_empty() {
1145 return Err(AuthError::config("Passkey origin cannot be empty"));
1146 }
1147 if config.timeout_ms == 0 {
1148 return Err(AuthError::config("Passkey timeout must be greater than 0"));
1149 }
1150 match config.user_verification.as_str() {
1151 "required" | "preferred" | "discouraged" => {}
1152 _ => {
1153 return Err(AuthError::config(
1154 "Invalid passkey user_verification value",
1155 ));
1156 }
1157 }
1158 url::Url::parse(&config.origin).map_err(|e| {
1159 AuthError::config(format!("Invalid passkey origin URL: {e}"))
1160 })?;
1161 }
1162 #[cfg(not(feature = "passkeys"))]
1163 "passkey" => {
1164 return Err(AuthError::config(
1165 "Passkey method config is present but the 'passkeys' feature is not enabled",
1166 ));
1167 }
1168 "enhanced_device_flow" => {
1169 let client_id = raw_config
1170 .get("client_id")
1171 .and_then(serde_json::Value::as_str)
1172 .unwrap_or_default();
1173 let auth_url = raw_config
1174 .get("auth_url")
1175 .and_then(serde_json::Value::as_str)
1176 .unwrap_or_default();
1177 let token_url = raw_config
1178 .get("token_url")
1179 .and_then(serde_json::Value::as_str)
1180 .unwrap_or_default();
1181 let device_auth_url = raw_config
1182 .get("device_auth_url")
1183 .and_then(serde_json::Value::as_str)
1184 .unwrap_or_default();
1185
1186 if client_id.trim().is_empty()
1187 || auth_url.trim().is_empty()
1188 || token_url.trim().is_empty()
1189 || device_auth_url.trim().is_empty()
1190 {
1191 return Err(AuthError::config(
1192 "Enhanced device flow config requires non-empty client_id, auth_url, token_url, and device_auth_url",
1193 ));
1194 }
1195 }
1196 _ => {}
1197 }
1198 }
1199
1200 Ok(())
1201 }
1202
1203 fn validate_jwt_secret(&self) -> Result<()> {
1205 let env_secret = std::env::var("JWT_SECRET").ok();
1207 let jwt_secret = self
1208 .security
1209 .secret_key
1210 .as_ref()
1211 .or(self.secret.as_ref())
1212 .or(env_secret.as_ref());
1213
1214 if let Some(secret) = jwt_secret {
1215 if !self.is_test_environment() && secret.len() < 32 {
1218 return Err(AuthError::config(
1219 "JWT secret must be at least 32 characters for security. \
1220 Generate with: openssl rand -base64 32",
1221 ));
1222 }
1223
1224 if !self.is_test_environment() {
1227 let mut char_counts = std::collections::HashMap::new();
1228 for c in secret.chars() {
1229 *char_counts.entry(c).or_insert(0u32) += 1;
1230 }
1231 let len = secret.len() as f64;
1232 let entropy: f64 = char_counts
1233 .values()
1234 .map(|&count| {
1235 let p = count as f64 / len;
1236 -p * p.log2()
1237 })
1238 .sum();
1239 if entropy < 3.5 {
1240 return Err(AuthError::config(
1241 "JWT secret has insufficient entropy (too predictable). \
1242 Use a cryptographically secure random string.",
1243 ));
1244 }
1245 }
1246
1247 if secret.len() < 44
1249 && secret
1250 .chars()
1251 .all(|c| c.is_alphanumeric() || c == '+' || c == '/' || c == '=')
1252 {
1253 tracing::warn!(
1254 "JWT secret may be too short for optimal security. \
1255 Consider using at least 44 characters (32 bytes base64-encoded)."
1256 );
1257 }
1258 } else if self.is_production_environment() {
1259 return Err(AuthError::config(
1260 "JWT secret is required for production environments. \
1261 Set JWT_SECRET environment variable or configure security.secret_key",
1262 ));
1263 }
1264
1265 Ok(())
1266 }
1267
1268 fn validate_production_security(&self) -> Result<()> {
1270 if self.security.min_password_length < 8 {
1272 return Err(AuthError::config(
1273 "Production environments require minimum password length of 8 characters",
1274 ));
1275 }
1276
1277 if !self.security.require_password_complexity {
1278 tracing::warn!("Production deployment should enable password complexity requirements");
1279 }
1280
1281 if !self.security.secure_cookies {
1283 return Err(AuthError::config(
1284 "Production environments must use secure cookies (HTTPS required)",
1285 ));
1286 }
1287
1288 if !self.rate_limiting.enabled {
1290 tracing::warn!("Production deployment should enable rate limiting for security");
1291 }
1292
1293 if !self.audit.enabled {
1295 return Err(AuthError::config(
1296 "Production environments require audit logging for compliance",
1297 ));
1298 }
1299
1300 Ok(())
1301 }
1302
1303 fn validate_storage_config(&self) -> Result<()> {
1305 match &self.storage {
1306 StorageConfig::Memory => {
1307 if self.is_production_environment() && !self.is_test_environment() {
1308 return Err(AuthError::config(
1309 "Memory storage is not suitable for production environments. \
1310 Use PostgreSQL, Redis, MySQL, or SQLite storage.",
1311 ));
1312 }
1313 }
1314 #[cfg(feature = "mysql-storage")]
1315 StorageConfig::MySQL { .. } => {
1316 tracing::warn!(
1317 "MySQL storage has known RSA vulnerability (RUSTSEC-2023-0071). \
1318 Consider using PostgreSQL for enhanced security."
1319 );
1320 }
1321 _ => {} }
1323
1324 Ok(())
1325 }
1326
1327 fn is_production_environment(&self) -> bool {
1329 if self.force_production_mode {
1331 return true;
1332 }
1333
1334 if let Ok(env) = std::env::var("ENVIRONMENT")
1336 && (env.to_lowercase() == "production" || env.to_lowercase() == "prod")
1337 {
1338 return true;
1339 }
1340
1341 if let Ok(env) = std::env::var("ENV")
1342 && (env.to_lowercase() == "production" || env.to_lowercase() == "prod")
1343 {
1344 return true;
1345 }
1346
1347 if let Ok(env) = std::env::var("NODE_ENV")
1348 && env.to_lowercase() == "production"
1349 {
1350 return true;
1351 }
1352
1353 if let Ok(env) = std::env::var("RUST_ENV")
1354 && env.to_lowercase() == "production"
1355 {
1356 return true;
1357 }
1358
1359 if std::env::var("KUBERNETES_SERVICE_HOST").is_ok() {
1361 return true;
1362 }
1363
1364 if std::env::var("DOCKER_CONTAINER").is_ok() {
1365 return true;
1366 }
1367
1368 false
1369 }
1370
1371 fn is_test_environment(&self) -> bool {
1373 if self.force_production_mode {
1375 return false;
1376 }
1377 cfg!(test)
1379 || std::thread::current()
1380 .name()
1381 .is_some_and(|name| name.contains("test"))
1382 || std::env::var("RUST_TEST").is_ok()
1383 || std::env::var("ENVIRONMENT").as_deref() == Ok("test")
1384 || std::env::var("ENV").as_deref() == Ok("test")
1385 || std::env::args().any(|arg| arg.contains("test"))
1386 }
1387}
1388
1389impl RateLimitConfig {
1390 pub fn new(max_requests: u32, window: Duration) -> Self {
1403 Self {
1404 enabled: true,
1405 max_requests,
1406 window,
1407 burst: max_requests / 10, }
1409 }
1410
1411 pub fn disabled() -> Self {
1422 Self {
1423 enabled: false,
1424 ..Default::default()
1425 }
1426 }
1427
1428 pub fn per_second(max: u32) -> Self {
1439 Self::new(max, Duration::from_secs(1))
1440 }
1441
1442 pub fn per_minute(max: u32) -> Self {
1453 Self::new(max, Duration::from_secs(60))
1454 }
1455
1456 pub fn per_hour(max: u32) -> Self {
1467 Self::new(max, Duration::from_secs(3600))
1468 }
1469}
1470
1471impl SecurityConfig {
1472 pub fn secure() -> Self {
1485 Self {
1486 min_password_length: 12,
1487 require_password_complexity: true,
1488 password_hash_algorithm: PasswordHashAlgorithm::Argon2,
1489 jwt_algorithm: JwtAlgorithm::RS256,
1490 secret_key: None,
1491 previous_secret_key: None,
1492 secure_cookies: true,
1493 cookie_same_site: CookieSameSite::Strict,
1494 csrf_protection: true,
1495 session_timeout: Duration::from_secs(3600 * 8), }
1497 }
1498
1499 pub fn development() -> Self {
1513 Self {
1514 min_password_length: 6,
1515 require_password_complexity: false,
1516 password_hash_algorithm: PasswordHashAlgorithm::Bcrypt,
1517 jwt_algorithm: JwtAlgorithm::HS256,
1518 secret_key: None, previous_secret_key: None,
1520 secure_cookies: false,
1521 cookie_same_site: CookieSameSite::Lax,
1522 csrf_protection: false,
1523 session_timeout: Duration::from_secs(3600 * 24), }
1525 }
1526}
1527
1528#[derive(Debug)]
1565pub struct AuthConfigBuilder {
1566 config: AuthConfig,
1567}
1568
1569impl Default for AuthConfigBuilder {
1570 fn default() -> Self {
1571 Self::new()
1572 }
1573}
1574
1575impl AuthConfigBuilder {
1576 pub fn new() -> Self {
1578 Self {
1579 config: AuthConfig::default(),
1580 }
1581 }
1582
1583 pub fn tokens(self) -> TokenConfigBuilder {
1585 TokenConfigBuilder { builder: self }
1586 }
1587
1588 pub fn security(self) -> SecurityConfigBuilder {
1590 SecurityConfigBuilder { builder: self }
1591 }
1592
1593 pub fn storage(self) -> StorageConfigBuilder {
1595 StorageConfigBuilder { builder: self }
1596 }
1597
1598 pub fn features(self) -> FeatureConfigBuilder {
1600 FeatureConfigBuilder { builder: self }
1601 }
1602
1603 pub fn rate_limiting(self) -> RateLimitConfigBuilder {
1605 RateLimitConfigBuilder { builder: self }
1606 }
1607
1608 pub fn cors(self) -> CorsConfigBuilder {
1610 CorsConfigBuilder { builder: self }
1611 }
1612
1613 pub fn audit(self) -> AuditConfigBuilder {
1615 AuditConfigBuilder { builder: self }
1616 }
1617
1618 pub fn build(self) -> Result<AuthConfig> {
1620 let config = self.config;
1621 config.validate()?;
1622 Ok(config)
1623 }
1624}
1625
1626#[derive(Debug)]
1628pub struct TokenConfigBuilder {
1629 builder: AuthConfigBuilder,
1630}
1631
1632impl TokenConfigBuilder {
1633 pub fn lifetime(mut self, duration: Duration) -> Self {
1635 self.builder.config.token_lifetime = duration;
1636 self
1637 }
1638
1639 pub fn refresh_lifetime(mut self, duration: Duration) -> Self {
1641 self.builder.config.refresh_token_lifetime = duration;
1642 self
1643 }
1644
1645 pub fn issuer(mut self, issuer: impl Into<String>) -> Self {
1647 self.builder.config.issuer = issuer.into();
1648 self
1649 }
1650
1651 pub fn audience(mut self, audience: impl Into<String>) -> Self {
1653 self.builder.config.audience = audience.into();
1654 self
1655 }
1656
1657 pub fn secret(mut self, secret: impl Into<String>) -> Self {
1659 self.builder.config.secret = Some(secret.into());
1660 self
1661 }
1662
1663 pub fn done(self) -> AuthConfigBuilder {
1665 self.builder
1666 }
1667}
1668
1669#[derive(Debug)]
1671pub struct SecurityConfigBuilder {
1672 builder: AuthConfigBuilder,
1673}
1674
1675impl SecurityConfigBuilder {
1676 pub fn min_password_length(mut self, length: usize) -> Self {
1678 self.builder.config.security.min_password_length = length;
1679 self
1680 }
1681
1682 pub fn require_complexity(mut self, required: bool) -> Self {
1684 self.builder.config.security.require_password_complexity = required;
1685 self
1686 }
1687
1688 pub fn password_algorithm(mut self, algorithm: PasswordHashAlgorithm) -> Self {
1690 self.builder.config.security.password_hash_algorithm = algorithm;
1691 self
1692 }
1693
1694 pub fn jwt_algorithm(mut self, algorithm: JwtAlgorithm) -> Self {
1696 self.builder.config.security.jwt_algorithm = algorithm;
1697 self
1698 }
1699
1700 pub fn secure_cookies(mut self, enabled: bool) -> Self {
1702 self.builder.config.security.secure_cookies = enabled;
1703 self
1704 }
1705
1706 pub fn cookie_same_site(mut self, policy: CookieSameSite) -> Self {
1708 self.builder.config.security.cookie_same_site = policy;
1709 self
1710 }
1711
1712 pub fn csrf_protection(mut self, enabled: bool) -> Self {
1714 self.builder.config.security.csrf_protection = enabled;
1715 self
1716 }
1717
1718 pub fn session_timeout(mut self, timeout: Duration) -> Self {
1720 self.builder.config.security.session_timeout = timeout;
1721 self
1722 }
1723
1724 pub fn done(self) -> AuthConfigBuilder {
1726 self.builder
1727 }
1728}
1729
1730#[derive(Debug)]
1732pub struct StorageConfigBuilder {
1733 builder: AuthConfigBuilder,
1734}
1735
1736impl StorageConfigBuilder {
1737 pub fn memory(mut self) -> Self {
1739 self.builder.config.storage = StorageConfig::Memory;
1740 self
1741 }
1742
1743 #[cfg(feature = "redis-storage")]
1745 pub fn redis(mut self, url: impl Into<String>) -> Self {
1746 self.builder.config.storage = StorageConfig::Redis {
1747 url: url.into(),
1748 key_prefix: "auth:".to_string(),
1749 };
1750 self
1751 }
1752
1753 #[cfg(feature = "postgres-storage")]
1755 pub fn postgres(mut self, connection_string: impl Into<String>) -> Self {
1756 self.builder.config.storage = StorageConfig::Postgres {
1757 connection_string: connection_string.into(),
1758 table_prefix: "auth_".to_string(),
1759 };
1760 self
1761 }
1762
1763 #[cfg(feature = "mysql-storage")]
1765 pub fn mysql(mut self, connection_string: impl Into<String>) -> Self {
1766 self.builder.config.storage = StorageConfig::MySQL {
1767 connection_string: connection_string.into(),
1768 table_prefix: "auth_".to_string(),
1769 };
1770 self
1771 }
1772
1773 #[cfg(feature = "sqlite-storage")]
1775 pub fn sqlite(mut self, connection_string: impl Into<String>) -> Self {
1776 self.builder.config.storage = StorageConfig::Sqlite {
1777 connection_string: connection_string.into(),
1778 };
1779 self
1780 }
1781
1782 pub fn done(self) -> AuthConfigBuilder {
1784 self.builder
1785 }
1786}
1787
1788#[derive(Debug)]
1790pub struct FeatureConfigBuilder {
1791 builder: AuthConfigBuilder,
1792}
1793
1794impl FeatureConfigBuilder {
1795 pub fn enable_multi_factor(mut self, enabled: bool) -> Self {
1797 self.builder.config.enable_multi_factor = enabled;
1798 self
1799 }
1800
1801 pub fn enable_rbac(mut self, enabled: bool) -> Self {
1803 self.builder.config.enable_rbac = enabled;
1804 self
1805 }
1806
1807 pub fn enable_caching(mut self, enabled: bool) -> Self {
1809 self.builder.config.enable_caching = enabled;
1810 self
1811 }
1812
1813 pub fn enable_middleware(mut self, enabled: bool) -> Self {
1815 self.builder.config.enable_middleware = enabled;
1816 self
1817 }
1818
1819 pub fn max_failed_attempts(mut self, max: u32) -> Self {
1821 self.builder.config.max_failed_attempts = max;
1822 self
1823 }
1824
1825 pub fn done(self) -> AuthConfigBuilder {
1827 self.builder
1828 }
1829}
1830
1831#[derive(Debug)]
1833pub struct RateLimitConfigBuilder {
1834 builder: AuthConfigBuilder,
1835}
1836
1837impl RateLimitConfigBuilder {
1838 pub fn enabled(mut self, max_requests: u32, window: Duration) -> Self {
1840 self.builder.config.rate_limiting = RateLimitConfig::new(max_requests, window);
1841 self
1842 }
1843
1844 pub fn disabled(mut self) -> Self {
1846 self.builder.config.rate_limiting = RateLimitConfig::disabled();
1847 self
1848 }
1849
1850 pub fn max_requests(mut self, max: u32) -> Self {
1852 self.builder.config.rate_limiting.max_requests = max;
1853 self
1854 }
1855
1856 pub fn window(mut self, window: Duration) -> Self {
1858 self.builder.config.rate_limiting.window = window;
1859 self
1860 }
1861
1862 pub fn burst(mut self, burst: u32) -> Self {
1864 self.builder.config.rate_limiting.burst = burst;
1865 self
1866 }
1867
1868 pub fn done(self) -> AuthConfigBuilder {
1870 self.builder
1871 }
1872}
1873
1874#[derive(Debug)]
1876pub struct CorsConfigBuilder {
1877 builder: AuthConfigBuilder,
1878}
1879
1880impl CorsConfigBuilder {
1881 pub fn allow_origins<I, S>(mut self, origins: I) -> Self
1883 where
1884 I: IntoIterator<Item = S>,
1885 S: Into<String>,
1886 {
1887 self.builder.config.cors = CorsConfig::for_origins(origins);
1888 self
1889 }
1890
1891 pub fn disabled(mut self) -> Self {
1893 self.builder.config.cors.enabled = false;
1894 self
1895 }
1896
1897 pub fn allow_methods<I, S>(mut self, methods: I) -> Self
1899 where
1900 I: IntoIterator<Item = S>,
1901 S: Into<String>,
1902 {
1903 self.builder.config.cors.allowed_methods = methods.into_iter().map(Into::into).collect();
1904 self
1905 }
1906
1907 pub fn allow_headers<I, S>(mut self, headers: I) -> Self
1909 where
1910 I: IntoIterator<Item = S>,
1911 S: Into<String>,
1912 {
1913 self.builder.config.cors.allowed_headers = headers.into_iter().map(Into::into).collect();
1914 self
1915 }
1916
1917 pub fn max_age(mut self, seconds: u32) -> Self {
1919 self.builder.config.cors.max_age_secs = seconds;
1920 self
1921 }
1922
1923 pub fn done(self) -> AuthConfigBuilder {
1925 self.builder
1926 }
1927}
1928
1929#[derive(Debug)]
1931pub struct AuditConfigBuilder {
1932 builder: AuthConfigBuilder,
1933}
1934
1935impl AuditConfigBuilder {
1936 pub fn enabled(mut self) -> Self {
1938 self.builder.config.audit.enabled = true;
1939 self
1940 }
1941
1942 pub fn disabled(mut self) -> Self {
1944 self.builder.config.audit.enabled = false;
1945 self
1946 }
1947
1948 pub fn log_success(mut self, enabled: bool) -> Self {
1950 self.builder.config.audit.log_success = enabled;
1951 self
1952 }
1953
1954 pub fn log_failures(mut self, enabled: bool) -> Self {
1956 self.builder.config.audit.log_failures = enabled;
1957 self
1958 }
1959
1960 pub fn log_permissions(mut self, enabled: bool) -> Self {
1962 self.builder.config.audit.log_permissions = enabled;
1963 self
1964 }
1965
1966 pub fn log_tokens(mut self, enabled: bool) -> Self {
1968 self.builder.config.audit.log_tokens = enabled;
1969 self
1970 }
1971
1972 pub fn done(self) -> AuthConfigBuilder {
1974 self.builder
1975 }
1976}