use crate::error::{AuthError, Result};
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub database_url: String,
pub database_name: String,
pub jwt: JwtConfig,
pub password: PasswordConfig,
pub session: SessionConfig,
pub mfa: MfaConfig,
pub oauth2_providers: Vec<crate::models::OAuth2Provider>,
pub rate_limit: RateLimitConfig,
pub security: SecurityConfig,
pub risk: RiskConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JwtConfig {
pub algorithm: String,
pub private_key: String,
pub public_key: String,
pub issuer: String,
pub audience: String,
#[serde(with = "humantime_serde")]
pub access_token_ttl: Duration,
#[serde(with = "humantime_serde")]
pub refresh_token_ttl: Duration,
pub auto_rotate_keys: bool,
#[serde(with = "humantime_serde")]
pub rotation_interval: Duration,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PasswordConfig {
pub min_length: usize,
pub require_uppercase: bool,
pub require_lowercase: bool,
pub require_numbers: bool,
pub require_special: bool,
pub argon2_memory_cost: u32,
pub argon2_time_cost: u32,
pub argon2_parallelism: u32,
pub password_history: u32,
#[serde(with = "humantime_serde")]
pub password_expiration: Duration,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionConfig {
#[serde(with = "humantime_serde")]
pub idle_timeout: Duration,
#[serde(with = "humantime_serde")]
pub absolute_timeout: Duration,
pub max_concurrent_sessions: u32,
pub device_binding: bool,
pub ip_binding: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MfaConfig {
pub totp_issuer: String,
pub totp_period: u32,
pub totp_digits: u32,
pub webauthn_rp_id: String,
pub webauthn_rp_name: String,
pub webauthn_origin: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RateLimitConfig {
pub login_attempts_per_minute: u32,
pub registration_attempts_per_hour: u32,
pub password_reset_attempts_per_hour: u32,
pub lockout_threshold: u32,
#[serde(with = "humantime_serde")]
pub lockout_duration: Duration,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityConfig {
pub cors_enabled: bool,
pub cors_origins: Vec<String>,
pub https_only: bool,
pub hsts_enabled: bool,
pub trusted_proxies: Vec<String>,
pub audit_enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RiskConfig {
pub enabled: bool,
pub mfa_threshold: u8,
pub block_threshold: u8,
pub anomaly_detection: bool,
pub geo_velocity_check: bool,
pub max_travel_speed: f64,
}
impl Default for Config {
fn default() -> Self {
Self {
database_url: "http://localhost:8000".to_string(),
database_name: "auth".to_string(),
jwt: JwtConfig::default(),
password: PasswordConfig::default(),
session: SessionConfig::default(),
mfa: MfaConfig::default(),
oauth2_providers: vec![],
rate_limit: RateLimitConfig::default(),
security: SecurityConfig::default(),
risk: RiskConfig::default(),
}
}
}
impl Default for JwtConfig {
fn default() -> Self {
Self {
algorithm: "RS256".to_string(),
private_key: String::new(),
public_key: String::new(),
issuer: "avl-auth".to_string(),
audience: "avl-cloud".to_string(),
access_token_ttl: Duration::from_secs(900), refresh_token_ttl: Duration::from_secs(604800), auto_rotate_keys: true,
rotation_interval: Duration::from_secs(7776000), }
}
}
impl Default for PasswordConfig {
fn default() -> Self {
Self {
min_length: 12,
require_uppercase: true,
require_lowercase: true,
require_numbers: true,
require_special: true,
argon2_memory_cost: 65536, argon2_time_cost: 3,
argon2_parallelism: 4,
password_history: 5,
password_expiration: Duration::from_secs(7776000), }
}
}
impl Default for SessionConfig {
fn default() -> Self {
Self {
idle_timeout: Duration::from_secs(1800), absolute_timeout: Duration::from_secs(43200), max_concurrent_sessions: 5,
device_binding: true,
ip_binding: false,
}
}
}
impl Default for MfaConfig {
fn default() -> Self {
Self {
totp_issuer: "AVL Auth".to_string(),
totp_period: 30,
totp_digits: 6,
webauthn_rp_id: "avila.cloud".to_string(),
webauthn_rp_name: "AVL Cloud".to_string(),
webauthn_origin: "https://auth.avila.cloud".to_string(),
}
}
}
impl Default for RateLimitConfig {
fn default() -> Self {
Self {
login_attempts_per_minute: 5,
registration_attempts_per_hour: 3,
password_reset_attempts_per_hour: 3,
lockout_threshold: 5,
lockout_duration: Duration::from_secs(900), }
}
}
impl Default for SecurityConfig {
fn default() -> Self {
Self {
cors_enabled: true,
cors_origins: vec!["https://avila.cloud".to_string()],
https_only: true,
hsts_enabled: true,
trusted_proxies: vec![],
audit_enabled: true,
}
}
}
impl Default for RiskConfig {
fn default() -> Self {
Self {
enabled: true,
mfa_threshold: 60,
block_threshold: 90,
anomaly_detection: true,
geo_velocity_check: true,
max_travel_speed: 1000.0, }
}
}
impl Config {
pub fn from_env() -> Result<Self> {
Ok(Self {
database_url: std::env::var("AVILADB_URL")
.unwrap_or_else(|_| "http://localhost:8000".to_string()),
database_name: std::env::var("AVILADB_NAME")
.unwrap_or_else(|_| "auth".to_string()),
..Default::default()
})
}
pub fn validate(&self) -> Result<()> {
if self.jwt.private_key.is_empty() {
return Err(AuthError::ConfigError("JWT private key is required".to_string()));
}
if self.jwt.public_key.is_empty() {
return Err(AuthError::ConfigError("JWT public key is required".to_string()));
}
if self.password.min_length < 8 {
return Err(AuthError::ConfigError("Minimum password length must be at least 8".to_string()));
}
Ok(())
}
}