threatflux_core/
config.rs1use std::env;
2
3use crate::error::AuthError;
4
5#[derive(Debug, Clone)]
6pub struct PostgresStoreConfig {
7 pub url: String,
8 pub max_connections: Option<u32>,
9}
10
11impl PostgresStoreConfig {
12 #[must_use]
13 pub fn new(url: impl Into<String>) -> Self {
14 Self {
15 url: url.into(),
16 max_connections: None,
17 }
18 }
19
20 #[must_use]
21 pub const fn with_max_connections(mut self, max: u32) -> Self {
22 self.max_connections = Some(max);
23 self
24 }
25}
26
27#[derive(Debug, Clone)]
28pub struct AuthConfig {
29 pub secret: String,
30 pub database: Option<PostgresStoreConfig>,
31}
32
33impl AuthConfig {
34 pub fn from_env() -> Result<Self, AuthError> {
35 let secret = env::var("AUTH_SECRET")
36 .or_else(|_| env::var("JWT_SECRET"))
37 .map_err(|_| AuthError::BadRequest("AUTH_SECRET is required".to_string()))?;
38
39 let database_url = env::var("DATABASE_URL").ok();
40 let max_connections = env::var("DATABASE_MAX_CONNECTIONS")
41 .ok()
42 .and_then(|value| value.parse::<u32>().ok());
43
44 let database = database_url.map(|url| PostgresStoreConfig {
45 url,
46 max_connections,
47 });
48
49 Ok(Self { secret, database })
50 }
51}
52
53#[derive(Debug, Clone)]
59pub struct MfaConfig {
60 pub issuer: String,
62 pub backup_code_count: usize,
64 pub partial_session_expiry_minutes: i64,
66}
67
68impl Default for MfaConfig {
69 fn default() -> Self {
70 Self {
71 issuer: "ThreatFlux".to_string(),
72 backup_code_count: 8,
73 partial_session_expiry_minutes: 5,
74 }
75 }
76}
77
78impl MfaConfig {
79 pub fn from_env() -> Self {
80 Self {
81 issuer: env::var("MFA_ISSUER").unwrap_or_else(|_| "ThreatFlux".to_string()),
82 backup_code_count: env::var("MFA_BACKUP_CODE_COUNT")
83 .ok()
84 .and_then(|v| v.parse().ok())
85 .unwrap_or(8),
86 partial_session_expiry_minutes: env::var("MFA_PARTIAL_SESSION_EXPIRY_MINUTES")
87 .ok()
88 .and_then(|v| v.parse().ok())
89 .unwrap_or(5),
90 }
91 }
92}
93
94#[derive(Debug, Clone)]
100pub struct TokenConfig {
101 pub magic_link_expiry_minutes: i64,
103 pub password_reset_expiry_minutes: i64,
105 pub rate_limit_per_hour: i32,
107}
108
109impl Default for TokenConfig {
110 fn default() -> Self {
111 Self {
112 magic_link_expiry_minutes: 15,
113 password_reset_expiry_minutes: 60,
114 rate_limit_per_hour: 3,
115 }
116 }
117}
118
119impl TokenConfig {
120 pub fn from_env() -> Self {
121 Self {
122 magic_link_expiry_minutes: env::var("MAGIC_LINK_EXPIRY_MINUTES")
123 .ok()
124 .and_then(|v| v.parse().ok())
125 .unwrap_or(15),
126 password_reset_expiry_minutes: env::var("PASSWORD_RESET_EXPIRY_MINUTES")
127 .ok()
128 .and_then(|v| v.parse().ok())
129 .unwrap_or(60),
130 rate_limit_per_hour: env::var("TOKEN_RATE_LIMIT_PER_HOUR")
131 .ok()
132 .and_then(|v| v.parse().ok())
133 .unwrap_or(3),
134 }
135 }
136}
137
138#[derive(Debug, Clone)]
144pub struct DeviceConfig {
145 pub trust_duration_days: i64,
147}
148
149impl Default for DeviceConfig {
150 fn default() -> Self {
151 Self {
152 trust_duration_days: 90,
153 }
154 }
155}
156
157impl DeviceConfig {
158 pub fn from_env() -> Self {
159 Self {
160 trust_duration_days: env::var("DEVICE_TRUST_DURATION_DAYS")
161 .ok()
162 .and_then(|v| v.parse().ok())
163 .unwrap_or(90),
164 }
165 }
166}
167
168#[derive(Debug, Clone)]
174pub struct WebAuthnConfig {
175 pub rp_id: String,
177 pub rp_name: String,
179 pub rp_origin: String,
181 pub challenge_ttl_minutes: i64,
183}
184
185impl Default for WebAuthnConfig {
186 fn default() -> Self {
187 Self {
188 rp_id: "localhost".to_string(),
189 rp_name: "ThreatFlux".to_string(),
190 rp_origin: "http://localhost:8080".to_string(),
191 challenge_ttl_minutes: 5,
192 }
193 }
194}
195
196impl WebAuthnConfig {
197 pub fn from_env() -> Self {
198 Self {
199 rp_id: env::var("WEBAUTHN_RP_ID").unwrap_or_else(|_| "localhost".to_string()),
200 rp_name: env::var("WEBAUTHN_RP_NAME").unwrap_or_else(|_| "ThreatFlux".to_string()),
201 rp_origin: env::var("WEBAUTHN_RP_ORIGIN")
202 .unwrap_or_else(|_| "http://localhost:8080".to_string()),
203 challenge_ttl_minutes: env::var("WEBAUTHN_CHALLENGE_TTL_MINUTES")
204 .ok()
205 .and_then(|v| v.parse().ok())
206 .unwrap_or(5),
207 }
208 }
209}
210
211#[derive(Debug, Clone)]
217pub struct EncryptionConfig {
218 pub key: String,
220}
221
222impl EncryptionConfig {
223 pub fn from_env() -> Result<Self, AuthError> {
224 let key = env::var("ENCRYPTION_KEY")
225 .or_else(|_| env::var("PLAID_ENCRYPTION_KEY"))
226 .map_err(|_| AuthError::BadRequest("ENCRYPTION_KEY is required".to_string()))?;
227
228 if key.len() != 64 {
229 return Err(AuthError::BadRequest(
230 "ENCRYPTION_KEY must be 64 hex characters (32 bytes)".to_string(),
231 ));
232 }
233
234 Ok(Self { key })
235 }
236}