use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConfigPreset {
Production,
Development,
HighSecurity,
FromEnv,
}
#[derive(Debug, Clone)]
pub struct NonceConfig {
pub storage_ttl: Duration,
pub time_window: Duration,
}
impl Default for NonceConfig {
fn default() -> Self {
Self {
storage_ttl: Duration::from_secs(
std::env::var("NONCE_AUTH_STORAGE_TTL")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(300),
),
time_window: Duration::from_secs(
std::env::var("NONCE_AUTH_DEFAULT_TIME_WINDOW")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(60),
),
}
}
}
impl NonceConfig {
pub fn validate(&self) -> Vec<String> {
let mut warnings = Vec::new();
if self.storage_ttl.as_secs() < 60 {
warnings
.push("Very short storage TTL (< 1 minute) may cause usability issues".to_string());
}
if self.storage_ttl.as_secs() > 3600 {
warnings.push("Long storage TTL (> 1 hour) may increase security risk".to_string());
}
if self.time_window.as_secs() < 30 {
warnings.push(
"Very short time window (< 30 seconds) may cause clock sync issues".to_string(),
);
}
if self.time_window.as_secs() > 300 {
warnings
.push("Long time window (> 5 minutes) may increase replay attack risk".to_string());
}
if self.storage_ttl.as_secs() < self.time_window.as_secs() * 2 {
warnings.push(
"Storage TTL should be at least twice the time window for optimal security"
.to_string(),
);
}
warnings
}
pub fn summary(&self) -> String {
format!(
"NonceConfig {{ Storage TTL: {}s, Time Window: {}s }}",
self.storage_ttl.as_secs(),
self.time_window.as_secs(),
)
}
}
impl From<ConfigPreset> for NonceConfig {
fn from(preset: ConfigPreset) -> Self {
match preset {
ConfigPreset::Production => Self {
storage_ttl: Duration::from_secs(300),
time_window: Duration::from_secs(60),
},
ConfigPreset::Development => Self {
storage_ttl: Duration::from_secs(600),
time_window: Duration::from_secs(120),
},
ConfigPreset::HighSecurity => Self {
storage_ttl: Duration::from_secs(120),
time_window: Duration::from_secs(30),
},
ConfigPreset::FromEnv => Self::default(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn clear_env_vars() {
unsafe {
std::env::remove_var("NONCE_AUTH_DEFAULT_TTL");
std::env::remove_var("NONCE_AUTH_DEFAULT_TIME_WINDOW");
}
}
#[test]
fn test_default_configuration() {
let config = NonceConfig::from(ConfigPreset::Production);
assert_eq!(config.storage_ttl.as_secs(), 300);
assert_eq!(config.time_window.as_secs(), 60);
}
#[test]
fn test_environment_variable_override() {
let config = NonceConfig {
storage_ttl: Duration::from_secs(600),
time_window: Duration::from_secs(120),
};
assert_eq!(config.storage_ttl.as_secs(), 600);
assert_eq!(config.time_window.as_secs(), 120);
}
#[test]
fn test_production_preset() {
let config = NonceConfig::from(ConfigPreset::Production);
assert_eq!(config.storage_ttl.as_secs(), 300);
assert_eq!(config.time_window.as_secs(), 60);
}
#[test]
fn test_development_preset() {
let config = NonceConfig::from(ConfigPreset::Development);
assert_eq!(config.storage_ttl.as_secs(), 600);
assert_eq!(config.time_window.as_secs(), 120);
}
#[test]
fn test_high_security_preset() {
let config = NonceConfig::from(ConfigPreset::HighSecurity);
assert_eq!(config.storage_ttl.as_secs(), 120);
assert_eq!(config.time_window.as_secs(), 30);
}
#[test]
fn test_from_env() {
clear_env_vars();
unsafe {
std::env::set_var("NONCE_AUTH_STORAGE_TTL", "900");
std::env::set_var("NONCE_AUTH_DEFAULT_TIME_WINDOW", "180");
}
let config = NonceConfig::from(ConfigPreset::FromEnv);
assert_eq!(config.storage_ttl.as_secs(), 900);
assert_eq!(config.time_window.as_secs(), 180);
clear_env_vars();
}
#[test]
fn test_validation_valid_config() {
let config = NonceConfig::from(ConfigPreset::Production);
let warnings = config.validate();
assert!(warnings.is_empty());
}
#[test]
fn test_validation_ttl_warnings() {
let config = NonceConfig {
storage_ttl: Duration::from_secs(30),
time_window: Duration::from_secs(60),
};
let warnings = config.validate();
assert!(!warnings.is_empty());
assert!(
warnings
.iter()
.any(|w| w.contains("Very short storage TTL"))
);
let config = NonceConfig {
storage_ttl: Duration::from_secs(7200),
time_window: Duration::from_secs(60),
};
let warnings = config.validate();
assert!(!warnings.is_empty());
assert!(warnings.iter().any(|w| w.contains("Long storage TTL")));
}
#[test]
fn test_validation_time_window_warnings() {
let config = NonceConfig {
storage_ttl: Duration::from_secs(300),
time_window: Duration::from_secs(15),
};
let warnings = config.validate();
assert!(!warnings.is_empty());
assert!(
warnings
.iter()
.any(|w| w.contains("Very short time window"))
);
let config = NonceConfig {
storage_ttl: Duration::from_secs(300),
time_window: Duration::from_secs(600),
};
let warnings = config.validate();
assert!(!warnings.is_empty());
assert!(warnings.iter().any(|w| w.contains("Long time window")));
}
}