nonce_auth/nonce/
config.rs1use std::time::Duration;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum ConfigPreset {
9 Production,
15
16 Development,
22
23 HighSecurity,
29
30 FromEnv,
36}
37
38#[derive(Debug, Clone)]
65pub struct NonceConfig {
66 pub storage_ttl: Duration,
68 pub time_window: Duration,
70}
71
72impl Default for NonceConfig {
73 fn default() -> Self {
74 Self {
75 storage_ttl: Duration::from_secs(
76 std::env::var("NONCE_AUTH_STORAGE_TTL")
77 .ok()
78 .and_then(|s| s.parse().ok())
79 .unwrap_or(300),
80 ),
81 time_window: Duration::from_secs(
82 std::env::var("NONCE_AUTH_DEFAULT_TIME_WINDOW")
83 .ok()
84 .and_then(|s| s.parse().ok())
85 .unwrap_or(60),
86 ),
87 }
88 }
89}
90
91impl NonceConfig {
92 pub fn validate(&self) -> Vec<String> {
98 let mut warnings = Vec::new();
99
100 if self.storage_ttl.as_secs() < 60 {
102 warnings
103 .push("Very short storage TTL (< 1 minute) may cause usability issues".to_string());
104 }
105 if self.storage_ttl.as_secs() > 3600 {
106 warnings.push("Long storage TTL (> 1 hour) may increase security risk".to_string());
107 }
108
109 if self.time_window.as_secs() < 30 {
111 warnings.push(
112 "Very short time window (< 30 seconds) may cause clock sync issues".to_string(),
113 );
114 }
115 if self.time_window.as_secs() > 300 {
116 warnings
117 .push("Long time window (> 5 minutes) may increase replay attack risk".to_string());
118 }
119
120 if self.storage_ttl.as_secs() < self.time_window.as_secs() * 2 {
122 warnings.push(
123 "Storage TTL should be at least twice the time window for optimal security"
124 .to_string(),
125 );
126 }
127
128 warnings
129 }
130
131 pub fn summary(&self) -> String {
133 format!(
134 "NonceConfig {{ Storage TTL: {}s, Time Window: {}s }}",
135 self.storage_ttl.as_secs(),
136 self.time_window.as_secs(),
137 )
138 }
139}
140
141impl From<ConfigPreset> for NonceConfig {
142 fn from(preset: ConfigPreset) -> Self {
143 match preset {
144 ConfigPreset::Production => Self {
145 storage_ttl: Duration::from_secs(300),
146 time_window: Duration::from_secs(60),
147 },
148 ConfigPreset::Development => Self {
149 storage_ttl: Duration::from_secs(600),
150 time_window: Duration::from_secs(120),
151 },
152 ConfigPreset::HighSecurity => Self {
153 storage_ttl: Duration::from_secs(120),
154 time_window: Duration::from_secs(30),
155 },
156 ConfigPreset::FromEnv => Self::default(),
157 }
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 fn clear_env_vars() {
166 unsafe {
167 std::env::remove_var("NONCE_AUTH_DEFAULT_TTL");
168 std::env::remove_var("NONCE_AUTH_DEFAULT_TIME_WINDOW");
169 }
170 }
171
172 #[test]
173 fn test_default_configuration() {
174 let config = NonceConfig::from(ConfigPreset::Production);
176 assert_eq!(config.storage_ttl.as_secs(), 300);
177 assert_eq!(config.time_window.as_secs(), 60);
178 }
179
180 #[test]
181 fn test_environment_variable_override() {
182 let config = NonceConfig {
184 storage_ttl: Duration::from_secs(600),
185 time_window: Duration::from_secs(120),
186 };
187
188 assert_eq!(config.storage_ttl.as_secs(), 600);
189 assert_eq!(config.time_window.as_secs(), 120);
190 }
191
192 #[test]
193 fn test_production_preset() {
194 let config = NonceConfig::from(ConfigPreset::Production);
195 assert_eq!(config.storage_ttl.as_secs(), 300);
196 assert_eq!(config.time_window.as_secs(), 60);
197 }
198
199 #[test]
200 fn test_development_preset() {
201 let config = NonceConfig::from(ConfigPreset::Development);
202 assert_eq!(config.storage_ttl.as_secs(), 600);
203 assert_eq!(config.time_window.as_secs(), 120);
204 }
205
206 #[test]
207 fn test_high_security_preset() {
208 let config = NonceConfig::from(ConfigPreset::HighSecurity);
209 assert_eq!(config.storage_ttl.as_secs(), 120);
210 assert_eq!(config.time_window.as_secs(), 30);
211 }
212
213 #[test]
214 fn test_from_env() {
215 clear_env_vars();
216
217 unsafe {
218 std::env::set_var("NONCE_AUTH_STORAGE_TTL", "900");
219 std::env::set_var("NONCE_AUTH_DEFAULT_TIME_WINDOW", "180");
220 }
221
222 let config = NonceConfig::from(ConfigPreset::FromEnv);
223 assert_eq!(config.storage_ttl.as_secs(), 900);
224 assert_eq!(config.time_window.as_secs(), 180);
225
226 clear_env_vars();
227 }
228
229 #[test]
230 fn test_validation_valid_config() {
231 let config = NonceConfig::from(ConfigPreset::Production);
232 let warnings = config.validate();
233 assert!(warnings.is_empty());
234 }
235
236 #[test]
237 fn test_validation_ttl_warnings() {
238 let config = NonceConfig {
240 storage_ttl: Duration::from_secs(30),
241 time_window: Duration::from_secs(60),
242 };
243 let warnings = config.validate();
244 assert!(!warnings.is_empty());
245 assert!(
246 warnings
247 .iter()
248 .any(|w| w.contains("Very short storage TTL"))
249 );
250
251 let config = NonceConfig {
253 storage_ttl: Duration::from_secs(7200),
254 time_window: Duration::from_secs(60),
255 };
256 let warnings = config.validate();
257 assert!(!warnings.is_empty());
258 assert!(warnings.iter().any(|w| w.contains("Long storage TTL")));
259 }
260
261 #[test]
262 fn test_validation_time_window_warnings() {
263 let config = NonceConfig {
265 storage_ttl: Duration::from_secs(300),
266 time_window: Duration::from_secs(15),
267 };
268 let warnings = config.validate();
269 assert!(!warnings.is_empty());
270 assert!(
271 warnings
272 .iter()
273 .any(|w| w.contains("Very short time window"))
274 );
275
276 let config = NonceConfig {
278 storage_ttl: Duration::from_secs(300),
279 time_window: Duration::from_secs(600),
280 };
281 let warnings = config.validate();
282 assert!(!warnings.is_empty());
283 assert!(warnings.iter().any(|w| w.contains("Long time window")));
284 }
285}