nonce_auth/nonce/
config.rs1use std::time::Duration;
2
3#[derive(Debug, Clone)]
30pub struct NonceConfig {
31 pub default_ttl: Duration,
33 pub time_window: Duration,
35}
36
37impl Default for NonceConfig {
38 fn default() -> Self {
39 Self {
40 default_ttl: Duration::from_secs(
41 std::env::var("NONCE_AUTH_DEFAULT_TTL")
42 .ok()
43 .and_then(|s| s.parse().ok())
44 .unwrap_or(300),
45 ),
46 time_window: Duration::from_secs(
47 std::env::var("NONCE_AUTH_DEFAULT_TIME_WINDOW")
48 .ok()
49 .and_then(|s| s.parse().ok())
50 .unwrap_or(60),
51 ),
52 }
53 }
54}
55
56impl NonceConfig {
57 pub fn from_env() -> Self {
70 Self::default()
71 }
72
73 pub fn production() -> Self {
79 Self {
80 default_ttl: Duration::from_secs(300),
81 time_window: Duration::from_secs(60),
82 }
83 }
84
85 pub fn development() -> Self {
91 Self {
92 default_ttl: Duration::from_secs(600),
93 time_window: Duration::from_secs(120),
94 }
95 }
96
97 pub fn high_security() -> Self {
103 Self {
104 default_ttl: Duration::from_secs(120),
105 time_window: Duration::from_secs(30),
106 }
107 }
108
109 pub fn validate(&self) -> Vec<String> {
115 let mut warnings = Vec::new();
116
117 if self.default_ttl.as_secs() < 60 {
119 warnings.push("Very short TTL (< 1 minute) may cause usability issues".to_string());
120 }
121 if self.default_ttl.as_secs() > 3600 {
122 warnings.push("Long TTL (> 1 hour) may increase security risk".to_string());
123 }
124
125 if self.time_window.as_secs() < 30 {
127 warnings.push(
128 "Very short time window (< 30 seconds) may cause clock sync issues".to_string(),
129 );
130 }
131 if self.time_window.as_secs() > 300 {
132 warnings
133 .push("Long time window (> 5 minutes) may increase replay attack risk".to_string());
134 }
135
136 if self.default_ttl.as_secs() < self.time_window.as_secs() * 2 {
138 warnings.push(
139 "TTL should be at least twice the time window for optimal security".to_string(),
140 );
141 }
142
143 warnings
144 }
145
146 pub fn summary(&self) -> String {
148 format!(
149 "NonceConfig {{ TTL: {}s, Time Window: {}s }}",
150 self.default_ttl.as_secs(),
151 self.time_window.as_secs(),
152 )
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 fn clear_env_vars() {
161 unsafe {
162 std::env::remove_var("NONCE_AUTH_DEFAULT_TTL");
163 std::env::remove_var("NONCE_AUTH_DEFAULT_TIME_WINDOW");
164 }
165 }
166
167 #[test]
168 fn test_default_configuration() {
169 let config = NonceConfig::production();
171 assert_eq!(config.default_ttl.as_secs(), 300);
172 assert_eq!(config.time_window.as_secs(), 60);
173 }
174
175 #[test]
176 fn test_environment_variable_override() {
177 let config = NonceConfig {
179 default_ttl: Duration::from_secs(600),
180 time_window: Duration::from_secs(120),
181 };
182
183 assert_eq!(config.default_ttl.as_secs(), 600);
184 assert_eq!(config.time_window.as_secs(), 120);
185 }
186
187 #[test]
188 fn test_production_preset() {
189 let config = NonceConfig::production();
190 assert_eq!(config.default_ttl.as_secs(), 300);
191 assert_eq!(config.time_window.as_secs(), 60);
192 }
193
194 #[test]
195 fn test_development_preset() {
196 let config = NonceConfig::development();
197 assert_eq!(config.default_ttl.as_secs(), 600);
198 assert_eq!(config.time_window.as_secs(), 120);
199 }
200
201 #[test]
202 fn test_high_security_preset() {
203 let config = NonceConfig::high_security();
204 assert_eq!(config.default_ttl.as_secs(), 120);
205 assert_eq!(config.time_window.as_secs(), 30);
206 }
207
208 #[test]
209 fn test_from_env() {
210 clear_env_vars();
211
212 unsafe {
213 std::env::set_var("NONCE_AUTH_DEFAULT_TTL", "900");
214 std::env::set_var("NONCE_AUTH_DEFAULT_TIME_WINDOW", "180");
215 }
216
217 let config = NonceConfig::from_env();
218 assert_eq!(config.default_ttl.as_secs(), 900);
219 assert_eq!(config.time_window.as_secs(), 180);
220
221 clear_env_vars();
222 }
223
224 #[test]
225 fn test_validation_valid_config() {
226 let config = NonceConfig::production();
227 let warnings = config.validate();
228 assert!(warnings.is_empty());
229 }
230
231 #[test]
232 fn test_validation_ttl_warnings() {
233 let config = NonceConfig {
235 default_ttl: Duration::from_secs(30),
236 time_window: Duration::from_secs(60),
237 };
238 let warnings = config.validate();
239 assert!(!warnings.is_empty());
240 assert!(warnings.iter().any(|w| w.contains("Very short TTL")));
241
242 let config = NonceConfig {
244 default_ttl: Duration::from_secs(7200),
245 time_window: Duration::from_secs(60),
246 };
247 let warnings = config.validate();
248 assert!(!warnings.is_empty());
249 assert!(warnings.iter().any(|w| w.contains("Long TTL")));
250 }
251
252 #[test]
253 fn test_validation_time_window_warnings() {
254 let config = NonceConfig {
256 default_ttl: Duration::from_secs(300),
257 time_window: Duration::from_secs(15),
258 };
259 let warnings = config.validate();
260 assert!(!warnings.is_empty());
261 assert!(
262 warnings
263 .iter()
264 .any(|w| w.contains("Very short time window"))
265 );
266
267 let config = NonceConfig {
269 default_ttl: Duration::from_secs(300),
270 time_window: Duration::from_secs(600),
271 };
272 let warnings = config.validate();
273 assert!(!warnings.is_empty());
274 assert!(warnings.iter().any(|w| w.contains("Long time window")));
275 }
276
277 #[test]
278 fn test_validation_ttl_window_relationship() {
279 let config = NonceConfig {
280 default_ttl: Duration::from_secs(60),
281 time_window: Duration::from_secs(60),
282 };
283 let warnings = config.validate();
284 assert!(!warnings.is_empty());
285 assert!(warnings.iter().any(|w| w.contains("at least twice")));
286 }
287
288 #[test]
289 fn test_summary_format() {
290 let config = NonceConfig::production();
291 let summary = config.summary();
292 assert!(summary.contains("TTL: 300s"));
293 assert!(summary.contains("Time Window: 60s"));
294 }
295
296 #[test]
297 fn test_config_clone_and_debug() {
298 let config = NonceConfig::production();
299 let cloned = config.clone();
300 assert_eq!(config.default_ttl, cloned.default_ttl);
301 assert_eq!(config.time_window, cloned.time_window);
302
303 let debug_str = format!("{config:?}");
305 assert!(debug_str.contains("NonceConfig"));
306 }
307}