avl_auth/
config.rs

1//! Configuration for AVL Auth
2
3use crate::error::{AuthError, Result};
4use serde::{Deserialize, Serialize};
5use std::time::Duration;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Config {
9    /// AvilaDB connection string
10    pub database_url: String,
11
12    /// Database name
13    pub database_name: String,
14
15    /// JWT configuration
16    pub jwt: JwtConfig,
17
18    /// Password policy
19    pub password: PasswordConfig,
20
21    /// Session configuration
22    pub session: SessionConfig,
23
24    /// MFA configuration
25    pub mfa: MfaConfig,
26
27    /// OAuth2 providers
28    pub oauth2_providers: Vec<crate::models::OAuth2Provider>,
29
30    /// Rate limiting
31    pub rate_limit: RateLimitConfig,
32
33    /// Security settings
34    pub security: SecurityConfig,
35
36    /// Risk engine settings
37    pub risk: RiskConfig,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct JwtConfig {
42    /// JWT signing algorithm (RS256, RS384, RS512, ES256, ES384, HS256, HS512)
43    pub algorithm: String,
44
45    /// Private key for signing (PEM format)
46    pub private_key: String,
47
48    /// Public key for verification (PEM format)
49    pub public_key: String,
50
51    /// Token issuer
52    pub issuer: String,
53
54    /// Token audience
55    pub audience: String,
56
57    /// Access token expiration (seconds)
58    #[serde(with = "humantime_serde")]
59    pub access_token_ttl: Duration,
60
61    /// Refresh token expiration (seconds)
62    #[serde(with = "humantime_serde")]
63    pub refresh_token_ttl: Duration,
64
65    /// Enable automatic key rotation
66    pub auto_rotate_keys: bool,
67
68    /// Key rotation interval (days)
69    #[serde(with = "humantime_serde")]
70    pub rotation_interval: Duration,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct PasswordConfig {
75    /// Minimum password length
76    pub min_length: usize,
77
78    /// Require uppercase letters
79    pub require_uppercase: bool,
80
81    /// Require lowercase letters
82    pub require_lowercase: bool,
83
84    /// Require numbers
85    pub require_numbers: bool,
86
87    /// Require special characters
88    pub require_special: bool,
89
90    /// Argon2 memory cost (KiB)
91    pub argon2_memory_cost: u32,
92
93    /// Argon2 time cost (iterations)
94    pub argon2_time_cost: u32,
95
96    /// Argon2 parallelism
97    pub argon2_parallelism: u32,
98
99    /// Password history count
100    pub password_history: u32,
101
102    /// Password expiration (days, 0 = never)
103    #[serde(with = "humantime_serde")]
104    pub password_expiration: Duration,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct SessionConfig {
109    /// Session idle timeout
110    #[serde(with = "humantime_serde")]
111    pub idle_timeout: Duration,
112
113    /// Absolute session timeout
114    #[serde(with = "humantime_serde")]
115    pub absolute_timeout: Duration,
116
117    /// Maximum concurrent sessions per user
118    pub max_concurrent_sessions: u32,
119
120    /// Enable device binding
121    pub device_binding: bool,
122
123    /// Enable IP binding
124    pub ip_binding: bool,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct MfaConfig {
129    /// TOTP issuer name
130    pub totp_issuer: String,
131
132    /// TOTP period (seconds)
133    pub totp_period: u32,
134
135    /// TOTP digits
136    pub totp_digits: u32,
137
138    /// WebAuthn RP ID
139    pub webauthn_rp_id: String,
140
141    /// WebAuthn RP name
142    pub webauthn_rp_name: String,
143
144    /// WebAuthn origin
145    pub webauthn_origin: String,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct RateLimitConfig {
150    /// Login attempts per minute
151    pub login_attempts_per_minute: u32,
152
153    /// Registration attempts per hour
154    pub registration_attempts_per_hour: u32,
155
156    /// Password reset attempts per hour
157    pub password_reset_attempts_per_hour: u32,
158
159    /// Account lockout threshold
160    pub lockout_threshold: u32,
161
162    /// Account lockout duration
163    #[serde(with = "humantime_serde")]
164    pub lockout_duration: Duration,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct SecurityConfig {
169    /// Enable CORS
170    pub cors_enabled: bool,
171
172    /// Allowed CORS origins
173    pub cors_origins: Vec<String>,
174
175    /// Enable HTTPS only
176    pub https_only: bool,
177
178    /// Enable HSTS
179    pub hsts_enabled: bool,
180
181    /// Trusted proxy IPs
182    pub trusted_proxies: Vec<String>,
183
184    /// Enable audit logging
185    pub audit_enabled: bool,
186}
187
188#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct RiskConfig {
190    /// Enable risk-based authentication
191    pub enabled: bool,
192
193    /// Threshold for requiring MFA
194    pub mfa_threshold: u8,
195
196    /// Threshold for blocking
197    pub block_threshold: u8,
198
199    /// Enable anomaly detection
200    pub anomaly_detection: bool,
201
202    /// Enable geo-velocity check
203    pub geo_velocity_check: bool,
204
205    /// Maximum travel speed (km/h)
206    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), // 15 minutes
235            refresh_token_ttl: Duration::from_secs(604800), // 7 days
236            auto_rotate_keys: true,
237            rotation_interval: Duration::from_secs(7776000), // 90 days
238        }
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, // 64 MiB
251            argon2_time_cost: 3,
252            argon2_parallelism: 4,
253            password_history: 5,
254            password_expiration: Duration::from_secs(7776000), // 90 days
255        }
256    }
257}
258
259impl Default for SessionConfig {
260    fn default() -> Self {
261        Self {
262            idle_timeout: Duration::from_secs(1800), // 30 minutes
263            absolute_timeout: Duration::from_secs(43200), // 12 hours
264            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), // 15 minutes
292        }
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, // Roughly speed of a commercial aircraft
318        }
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}