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 storage(mut self, storage: StorageConfig) -> Self {
262 self.storage = storage;
263 self
264 }
265
266 #[cfg(feature = "redis-storage")]
268 pub fn redis_storage(mut self, url: impl Into<String>) -> Self {
269 self.storage = StorageConfig::Redis {
270 url: url.into(),
271 key_prefix: "auth:".to_string(),
272 };
273 self
274 }
275
276 pub fn rate_limiting(mut self, config: RateLimitConfig) -> Self {
278 self.rate_limiting = config;
279 self
280 }
281
282 pub fn security(mut self, config: SecurityConfig) -> Self {
284 self.security = config;
285 self
286 }
287
288 pub fn audit(mut self, config: AuditConfig) -> Self {
290 self.audit = config;
291 self
292 }
293
294 pub fn method_config(
296 mut self,
297 method_name: impl Into<String>,
298 config: impl Serialize,
299 ) -> Result<Self> {
300 let value = serde_json::to_value(config)
301 .map_err(|e| AuthError::config(format!("Failed to serialize method config: {e}")))?;
302
303 self.method_configs.insert(method_name.into(), value);
304 Ok(self)
305 }
306
307 pub fn get_method_config<T>(&self, method_name: &str) -> Result<Option<T>>
309 where
310 T: for<'de> Deserialize<'de>,
311 {
312 if let Some(value) = self.method_configs.get(method_name) {
313 let config = serde_json::from_value(value.clone())
314 .map_err(|e| AuthError::config(format!("Failed to deserialize method config: {e}")))?;
315 Ok(Some(config))
316 } else {
317 Ok(None)
318 }
319 }
320
321 pub fn validate(&self) -> Result<()> {
323 if self.token_lifetime.as_secs() == 0 {
325 return Err(AuthError::config("Token lifetime must be greater than 0"));
326 }
327
328 if self.refresh_token_lifetime.as_secs() == 0 {
329 return Err(AuthError::config("Refresh token lifetime must be greater than 0"));
330 }
331
332 if self.refresh_token_lifetime <= self.token_lifetime {
333 return Err(AuthError::config(
334 "Refresh token lifetime must be greater than token lifetime"
335 ));
336 }
337
338 if self.security.min_password_length < 4 {
340 return Err(AuthError::config(
341 "Minimum password length must be at least 4 characters"
342 ));
343 }
344
345 if self.rate_limiting.enabled && self.rate_limiting.max_requests == 0 {
347 return Err(AuthError::config(
348 "Rate limit max requests must be greater than 0 when enabled"
349 ));
350 }
351
352 Ok(())
353 }
354}
355
356impl RateLimitConfig {
357 pub fn new(max_requests: u32, window: Duration) -> Self {
359 Self {
360 enabled: true,
361 max_requests,
362 window,
363 burst: max_requests / 10, }
365 }
366
367 pub fn disabled() -> Self {
369 Self {
370 enabled: false,
371 ..Default::default()
372 }
373 }
374}
375
376impl SecurityConfig {
377 pub fn secure() -> Self {
379 Self {
380 min_password_length: 12,
381 require_password_complexity: true,
382 password_hash_algorithm: PasswordHashAlgorithm::Argon2,
383 jwt_algorithm: JwtAlgorithm::RS256,
384 secret_key: None,
385 secure_cookies: true,
386 cookie_same_site: CookieSameSite::Strict,
387 csrf_protection: true,
388 session_timeout: Duration::from_secs(3600 * 8), }
390 }
391
392 pub fn development() -> Self {
394 Self {
395 min_password_length: 6,
396 require_password_complexity: false,
397 password_hash_algorithm: PasswordHashAlgorithm::Bcrypt,
398 jwt_algorithm: JwtAlgorithm::HS256,
399 secret_key: Some("development-secret-key".to_string()),
400 secure_cookies: false,
401 cookie_same_site: CookieSameSite::Lax,
402 csrf_protection: false,
403 session_timeout: Duration::from_secs(3600 * 24), }
405 }
406}