1use crate::errors::{AuthError, Result};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::time::Duration;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct AuthConfig {
11 pub token_lifetime: Duration,
13
14 pub refresh_token_lifetime: Duration,
16
17 pub enable_multi_factor: bool,
19
20 pub storage: StorageConfig,
22
23 pub rate_limiting: RateLimitConfig,
25
26 pub security: SecurityConfig,
28
29 pub audit: AuditConfig,
31
32 pub method_configs: HashMap<String, serde_json::Value>,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38pub enum StorageConfig {
39 Memory,
41
42 #[cfg(feature = "redis-storage")]
44 Redis {
45 url: String,
46 key_prefix: String,
47 },
48
49 #[cfg(feature = "postgres-storage")]
51 Postgres {
52 connection_string: String,
53 table_prefix: String,
54 },
55
56 #[cfg(feature = "mysql-storage")]
58 MySQL {
59 connection_string: String,
60 table_prefix: String,
61 },
62
63 Custom(String),
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct RateLimitConfig {
70 pub enabled: bool,
72
73 pub max_requests: u32,
75
76 pub window: Duration,
78
79 pub burst: u32,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct SecurityConfig {
86 pub min_password_length: usize,
88
89 pub require_password_complexity: bool,
91
92 pub password_hash_algorithm: PasswordHashAlgorithm,
94
95 pub jwt_algorithm: JwtAlgorithm,
97
98 pub secret_key: Option<String>,
100
101 pub secure_cookies: bool,
103
104 pub cookie_same_site: CookieSameSite,
106
107 pub csrf_protection: bool,
109
110 pub session_timeout: Duration,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
116pub enum PasswordHashAlgorithm {
117 Argon2,
118 Bcrypt,
119 Scrypt,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
124pub enum JwtAlgorithm {
125 HS256,
126 HS384,
127 HS512,
128 RS256,
129 RS384,
130 RS512,
131 ES256,
132 ES384,
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
137pub enum CookieSameSite {
138 Strict,
139 Lax,
140 None,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct AuditConfig {
146 pub enabled: bool,
148
149 pub log_success: bool,
151
152 pub log_failures: bool,
154
155 pub log_permissions: bool,
157
158 pub log_tokens: bool,
160
161 pub storage: AuditStorage,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize)]
167pub enum AuditStorage {
168 Tracing,
170
171 File { path: String },
173
174 Database { connection_string: String },
176
177 External { endpoint: String, api_key: String },
179}
180
181impl Default for AuthConfig {
182 fn default() -> Self {
183 Self {
184 token_lifetime: Duration::from_secs(3600), refresh_token_lifetime: Duration::from_secs(86400 * 7), enable_multi_factor: false,
187 storage: StorageConfig::Memory,
188 rate_limiting: RateLimitConfig::default(),
189 security: SecurityConfig::default(),
190 audit: AuditConfig::default(),
191 method_configs: HashMap::new(),
192 }
193 }
194}
195
196impl Default for RateLimitConfig {
197 fn default() -> Self {
198 Self {
199 enabled: true,
200 max_requests: 100,
201 window: Duration::from_secs(60), burst: 10,
203 }
204 }
205}
206
207impl Default for SecurityConfig {
208 fn default() -> Self {
209 Self {
210 min_password_length: 8,
211 require_password_complexity: true,
212 password_hash_algorithm: PasswordHashAlgorithm::Argon2,
213 jwt_algorithm: JwtAlgorithm::HS256,
214 secret_key: None,
215 secure_cookies: true,
216 cookie_same_site: CookieSameSite::Lax,
217 csrf_protection: true,
218 session_timeout: Duration::from_secs(3600 * 24), }
220 }
221}
222
223impl Default for AuditConfig {
224 fn default() -> Self {
225 Self {
226 enabled: true,
227 log_success: true,
228 log_failures: true,
229 log_permissions: true,
230 log_tokens: false, storage: AuditStorage::Tracing,
232 }
233 }
234}
235
236impl AuthConfig {
237 pub fn new() -> Self {
239 Self::default()
240 }
241
242 pub fn token_lifetime(mut self, lifetime: Duration) -> Self {
244 self.token_lifetime = lifetime;
245 self
246 }
247
248 pub fn refresh_token_lifetime(mut self, lifetime: Duration) -> Self {
250 self.refresh_token_lifetime = lifetime;
251 self
252 }
253
254 pub fn enable_multi_factor(mut self, enabled: bool) -> Self {
256 self.enable_multi_factor = enabled;
257 self
258 }
259
260 pub fn require_mfa(mut self, required: bool) -> Self {
262 self.enable_multi_factor = required;
263 self
264 }
265
266 pub fn enable_caching(self, _enabled: bool) -> Self {
268 self
270 }
271
272 pub fn max_failed_attempts(self, _max: u32) -> Self {
274 self
276 }
277
278 pub fn enable_rbac(self, _enabled: bool) -> Self {
280 self
282 }
283
284 pub fn enable_security_audit(self, _enabled: bool) -> Self {
286 self
288 }
289
290 pub fn enable_middleware(self, _enabled: bool) -> Self {
292 self
294 }
295
296 pub fn storage(mut self, storage: StorageConfig) -> Self {
298 self.storage = storage;
299 self
300 }
301
302 #[cfg(feature = "redis-storage")]
304 pub fn redis_storage(mut self, url: impl Into<String>) -> Self {
305 self.storage = StorageConfig::Redis {
306 url: url.into(),
307 key_prefix: "auth:".to_string(),
308 };
309 self
310 }
311
312 pub fn rate_limiting(mut self, config: RateLimitConfig) -> Self {
314 self.rate_limiting = config;
315 self
316 }
317
318 pub fn security(mut self, config: SecurityConfig) -> Self {
320 self.security = config;
321 self
322 }
323
324 pub fn audit(mut self, config: AuditConfig) -> Self {
326 self.audit = config;
327 self
328 }
329
330 pub fn method_config(
332 mut self,
333 method_name: impl Into<String>,
334 config: impl Serialize,
335 ) -> Result<Self> {
336 let value = serde_json::to_value(config)
337 .map_err(|e| AuthError::config(format!("Failed to serialize method config: {e}")))?;
338
339 self.method_configs.insert(method_name.into(), value);
340 Ok(self)
341 }
342
343 pub fn get_method_config<T>(&self, method_name: &str) -> Result<Option<T>>
345 where
346 T: for<'de> Deserialize<'de>,
347 {
348 if let Some(value) = self.method_configs.get(method_name) {
349 let config = serde_json::from_value(value.clone())
350 .map_err(|e| AuthError::config(format!("Failed to deserialize method config: {e}")))?;
351 Ok(Some(config))
352 } else {
353 Ok(None)
354 }
355 }
356
357 pub fn validate(&self) -> Result<()> {
359 if self.token_lifetime.as_secs() == 0 {
361 return Err(AuthError::config("Token lifetime must be greater than 0"));
362 }
363
364 if self.refresh_token_lifetime.as_secs() == 0 {
365 return Err(AuthError::config("Refresh token lifetime must be greater than 0"));
366 }
367
368 if self.refresh_token_lifetime <= self.token_lifetime {
369 return Err(AuthError::config(
370 "Refresh token lifetime must be greater than token lifetime"
371 ));
372 }
373
374 if self.security.min_password_length < 4 {
376 return Err(AuthError::config(
377 "Minimum password length must be at least 4 characters"
378 ));
379 }
380
381 if self.rate_limiting.enabled && self.rate_limiting.max_requests == 0 {
383 return Err(AuthError::config(
384 "Rate limit max requests must be greater than 0 when enabled"
385 ));
386 }
387
388 Ok(())
389 }
390}
391
392impl RateLimitConfig {
393 pub fn new(max_requests: u32, window: Duration) -> Self {
395 Self {
396 enabled: true,
397 max_requests,
398 window,
399 burst: max_requests / 10, }
401 }
402
403 pub fn disabled() -> Self {
405 Self {
406 enabled: false,
407 ..Default::default()
408 }
409 }
410}
411
412impl SecurityConfig {
413 pub fn secure() -> Self {
415 Self {
416 min_password_length: 12,
417 require_password_complexity: true,
418 password_hash_algorithm: PasswordHashAlgorithm::Argon2,
419 jwt_algorithm: JwtAlgorithm::RS256,
420 secret_key: None,
421 secure_cookies: true,
422 cookie_same_site: CookieSameSite::Strict,
423 csrf_protection: true,
424 session_timeout: Duration::from_secs(3600 * 8), }
426 }
427
428 pub fn development() -> Self {
430 Self {
431 min_password_length: 6,
432 require_password_complexity: false,
433 password_hash_algorithm: PasswordHashAlgorithm::Bcrypt,
434 jwt_algorithm: JwtAlgorithm::HS256,
435 secret_key: Some("development-secret-key".to_string()),
436 secure_cookies: false,
437 cookie_same_site: CookieSameSite::Lax,
438 csrf_protection: false,
439 session_timeout: Duration::from_secs(3600 * 24), }
441 }
442}