1use crate::error::{AuthError, Result};
4use serde::{Deserialize, Serialize};
5use std::time::Duration;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Config {
9 pub database_url: String,
11
12 pub database_name: String,
14
15 pub jwt: JwtConfig,
17
18 pub password: PasswordConfig,
20
21 pub session: SessionConfig,
23
24 pub mfa: MfaConfig,
26
27 pub oauth2_providers: Vec<crate::models::OAuth2Provider>,
29
30 pub rate_limit: RateLimitConfig,
32
33 pub security: SecurityConfig,
35
36 pub risk: RiskConfig,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct JwtConfig {
42 pub algorithm: String,
44
45 pub private_key: String,
47
48 pub public_key: String,
50
51 pub issuer: String,
53
54 pub audience: String,
56
57 #[serde(with = "humantime_serde")]
59 pub access_token_ttl: Duration,
60
61 #[serde(with = "humantime_serde")]
63 pub refresh_token_ttl: Duration,
64
65 pub auto_rotate_keys: bool,
67
68 #[serde(with = "humantime_serde")]
70 pub rotation_interval: Duration,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct PasswordConfig {
75 pub min_length: usize,
77
78 pub require_uppercase: bool,
80
81 pub require_lowercase: bool,
83
84 pub require_numbers: bool,
86
87 pub require_special: bool,
89
90 pub argon2_memory_cost: u32,
92
93 pub argon2_time_cost: u32,
95
96 pub argon2_parallelism: u32,
98
99 pub password_history: u32,
101
102 #[serde(with = "humantime_serde")]
104 pub password_expiration: Duration,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct SessionConfig {
109 #[serde(with = "humantime_serde")]
111 pub idle_timeout: Duration,
112
113 #[serde(with = "humantime_serde")]
115 pub absolute_timeout: Duration,
116
117 pub max_concurrent_sessions: u32,
119
120 pub device_binding: bool,
122
123 pub ip_binding: bool,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct MfaConfig {
129 pub totp_issuer: String,
131
132 pub totp_period: u32,
134
135 pub totp_digits: u32,
137
138 pub webauthn_rp_id: String,
140
141 pub webauthn_rp_name: String,
143
144 pub webauthn_origin: String,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct RateLimitConfig {
150 pub login_attempts_per_minute: u32,
152
153 pub registration_attempts_per_hour: u32,
155
156 pub password_reset_attempts_per_hour: u32,
158
159 pub lockout_threshold: u32,
161
162 #[serde(with = "humantime_serde")]
164 pub lockout_duration: Duration,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct SecurityConfig {
169 pub cors_enabled: bool,
171
172 pub cors_origins: Vec<String>,
174
175 pub https_only: bool,
177
178 pub hsts_enabled: bool,
180
181 pub trusted_proxies: Vec<String>,
183
184 pub audit_enabled: bool,
186}
187
188#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct RiskConfig {
190 pub enabled: bool,
192
193 pub mfa_threshold: u8,
195
196 pub block_threshold: u8,
198
199 pub anomaly_detection: bool,
201
202 pub geo_velocity_check: bool,
204
205 pub max_travel_speed: f64,
207}
208
209impl Default for Config {
210 fn default() -> Self {
211 Self {
212 database_url: "http://localhost:8000".to_string(),
213 database_name: "auth".to_string(),
214 jwt: JwtConfig::default(),
215 password: PasswordConfig::default(),
216 session: SessionConfig::default(),
217 mfa: MfaConfig::default(),
218 oauth2_providers: vec![],
219 rate_limit: RateLimitConfig::default(),
220 security: SecurityConfig::default(),
221 risk: RiskConfig::default(),
222 }
223 }
224}
225
226impl Default for JwtConfig {
227 fn default() -> Self {
228 Self {
229 algorithm: "RS256".to_string(),
230 private_key: String::new(),
231 public_key: String::new(),
232 issuer: "avl-auth".to_string(),
233 audience: "avl-cloud".to_string(),
234 access_token_ttl: Duration::from_secs(900), refresh_token_ttl: Duration::from_secs(604800), auto_rotate_keys: true,
237 rotation_interval: Duration::from_secs(7776000), }
239 }
240}
241
242impl Default for PasswordConfig {
243 fn default() -> Self {
244 Self {
245 min_length: 12,
246 require_uppercase: true,
247 require_lowercase: true,
248 require_numbers: true,
249 require_special: true,
250 argon2_memory_cost: 65536, argon2_time_cost: 3,
252 argon2_parallelism: 4,
253 password_history: 5,
254 password_expiration: Duration::from_secs(7776000), }
256 }
257}
258
259impl Default for SessionConfig {
260 fn default() -> Self {
261 Self {
262 idle_timeout: Duration::from_secs(1800), absolute_timeout: Duration::from_secs(43200), max_concurrent_sessions: 5,
265 device_binding: true,
266 ip_binding: false,
267 }
268 }
269}
270
271impl Default for MfaConfig {
272 fn default() -> Self {
273 Self {
274 totp_issuer: "AVL Auth".to_string(),
275 totp_period: 30,
276 totp_digits: 6,
277 webauthn_rp_id: "avila.cloud".to_string(),
278 webauthn_rp_name: "AVL Cloud".to_string(),
279 webauthn_origin: "https://auth.avila.cloud".to_string(),
280 }
281 }
282}
283
284impl Default for RateLimitConfig {
285 fn default() -> Self {
286 Self {
287 login_attempts_per_minute: 5,
288 registration_attempts_per_hour: 3,
289 password_reset_attempts_per_hour: 3,
290 lockout_threshold: 5,
291 lockout_duration: Duration::from_secs(900), }
293 }
294}
295
296impl Default for SecurityConfig {
297 fn default() -> Self {
298 Self {
299 cors_enabled: true,
300 cors_origins: vec!["https://avila.cloud".to_string()],
301 https_only: true,
302 hsts_enabled: true,
303 trusted_proxies: vec![],
304 audit_enabled: true,
305 }
306 }
307}
308
309impl Default for RiskConfig {
310 fn default() -> Self {
311 Self {
312 enabled: true,
313 mfa_threshold: 60,
314 block_threshold: 90,
315 anomaly_detection: true,
316 geo_velocity_check: true,
317 max_travel_speed: 1000.0, }
319 }
320}
321
322impl Config {
323 pub fn from_env() -> Result<Self> {
324 Ok(Self {
325 database_url: std::env::var("AVILADB_URL")
326 .unwrap_or_else(|_| "http://localhost:8000".to_string()),
327 database_name: std::env::var("AVILADB_NAME")
328 .unwrap_or_else(|_| "auth".to_string()),
329 ..Default::default()
330 })
331 }
332
333 pub fn validate(&self) -> Result<()> {
334 if self.jwt.private_key.is_empty() {
335 return Err(AuthError::ConfigError("JWT private key is required".to_string()));
336 }
337
338 if self.jwt.public_key.is_empty() {
339 return Err(AuthError::ConfigError("JWT public key is required".to_string()));
340 }
341
342 if self.password.min_length < 8 {
343 return Err(AuthError::ConfigError("Minimum password length must be at least 8".to_string()));
344 }
345
346 Ok(())
347 }
348}