nonce_auth/nonce/
config.rs

1use std::time::Duration;
2
3/// Configuration for nonce authentication system.
4///
5/// This struct provides a centralized way to configure the security parameters
6/// of the nonce authentication system, including TTL and time window settings.
7///
8/// # Environment Variables
9///
10/// Configuration options can be set via environment variables:
11/// - `NONCE_AUTH_DEFAULT_TTL`: Default TTL in seconds (default: 300)
12/// - `NONCE_AUTH_DEFAULT_TIME_WINDOW`: Time window in seconds (default: 60)
13///
14/// # Example
15///
16/// ```rust
17/// use nonce_auth::nonce::NonceConfig;
18/// use std::time::Duration;
19///
20/// // Use default configuration
21/// let config = NonceConfig::default();
22///
23/// // Create custom configuration
24/// let config = NonceConfig {
25///     default_ttl: Duration::from_secs(600), // 10 minutes
26///     time_window: Duration::from_secs(120), // 2 minutes
27/// };
28/// ```
29#[derive(Debug, Clone)]
30pub struct NonceConfig {
31    /// Default time-to-live for nonce records
32    pub default_ttl: Duration,
33    /// Time window for timestamp validation
34    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    /// Creates a new configuration from environment variables.
58    ///
59    /// # Returns
60    ///
61    /// A `NonceConfig` instance with environment variable values.
62    ///
63    /// # Example
64    ///
65    /// ```bash
66    /// export NONCE_AUTH_DEFAULT_TTL=600
67    /// export NONCE_AUTH_DEFAULT_TIME_WINDOW=120
68    /// ```
69    pub fn from_env() -> Self {
70        Self::default()
71    }
72
73    /// Creates a production-ready configuration.
74    ///
75    /// Production settings prioritize security and stability:
76    /// - 5 minutes TTL (reasonable balance between security and usability)
77    /// - 1 minute time window (accounts for network delays and clock skew)
78    pub fn production() -> Self {
79        Self {
80            default_ttl: Duration::from_secs(300),
81            time_window: Duration::from_secs(60),
82        }
83    }
84
85    /// Creates a development configuration.
86    ///
87    /// Development settings prioritize convenience:
88    /// - 10 minutes TTL (longer window for testing)
89    /// - 2 minutes time window (more forgiving for local development)
90    pub fn development() -> Self {
91        Self {
92            default_ttl: Duration::from_secs(600),
93            time_window: Duration::from_secs(120),
94        }
95    }
96
97    /// Creates a high-security configuration.
98    ///
99    /// High-security settings prioritize maximum security:
100    /// - 2 minutes TTL (very short window to minimize exposure)
101    /// - 30 seconds time window (strict timing requirements)
102    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    /// Validates the configuration and returns any warnings.
110    ///
111    /// # Returns
112    ///
113    /// A vector of warning messages for potentially problematic settings.
114    pub fn validate(&self) -> Vec<String> {
115        let mut warnings = Vec::new();
116
117        // Check TTL settings
118        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        // Check time window settings
126        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        // Check relationship between TTL and time window
137        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    /// Returns a summary of the current configuration.
147    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        // Test production config which doesn't depend on env vars
170        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        // Test that custom config works without depending on environment
178        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        // Test very short TTL
234        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        // Test very long TTL
243        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        // Test very short time window
255        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        // Test very long time window
268        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        // Test Debug implementation
304        let debug_str = format!("{config:?}");
305        assert!(debug_str.contains("NonceConfig"));
306    }
307}