use serde_json::Value as JsonValue;
use tracing::{debug, info, warn};
use super::security_config::SecurityConfigFromSchema;
use crate::{AuthError, error::Result};
const MAX_SCHEMA_JSON_SIZE: usize = 10 * 1024 * 1024;
const MAX_SECURITY_CONFIG_SIZE: usize = 100 * 1024;
pub fn init_security_config(schema_json_str: &str) -> Result<SecurityConfigFromSchema> {
debug!("Parsing schema JSON for security configuration");
if schema_json_str.len() > MAX_SCHEMA_JSON_SIZE {
warn!(
"Schema JSON exceeds maximum size: {} > {} bytes",
schema_json_str.len(),
MAX_SCHEMA_JSON_SIZE
);
return Err(AuthError::ConfigError {
message: format!("Schema JSON exceeds maximum size of {} bytes", MAX_SCHEMA_JSON_SIZE),
});
}
let schema_json: JsonValue = serde_json::from_str(schema_json_str).map_err(|e| {
warn!("Failed to parse schema JSON: {e}");
AuthError::ConfigError {
message: format!("Invalid schema JSON: {e}"),
}
})?;
init_security_config_from_value(&schema_json)
}
fn init_security_config_from_value(schema_json: &JsonValue) -> Result<SecurityConfigFromSchema> {
debug!("Initializing security configuration from schema");
let security_value = schema_json.get("security").ok_or_else(|| {
warn!("No security configuration found in schema, using defaults");
AuthError::ConfigError {
message: "Missing security configuration in schema".to_string(),
}
})?;
let security_json_str = security_value.to_string();
if security_json_str.len() > MAX_SECURITY_CONFIG_SIZE {
warn!(
"Security configuration exceeds maximum size: {} > {} bytes",
security_json_str.len(),
MAX_SECURITY_CONFIG_SIZE
);
return Err(AuthError::ConfigError {
message: format!(
"Security configuration exceeds maximum size of {} bytes",
MAX_SECURITY_CONFIG_SIZE
),
});
}
let mut config = SecurityConfigFromSchema::from_json(security_value).map_err(|e| {
warn!("Failed to parse security configuration: {e}");
AuthError::ConfigError {
message: format!("Invalid security configuration: {e}"),
}
})?;
info!("Security configuration loaded from schema");
config.apply_env_overrides();
debug!("Security environment variable overrides applied");
Ok(config)
}
pub fn init_default_security_config() -> SecurityConfigFromSchema {
info!("Initializing default security configuration");
let mut config = SecurityConfigFromSchema::default();
config.apply_env_overrides();
debug!("Default security configuration applied with environment overrides");
config
}
pub fn log_security_config(config: &SecurityConfigFromSchema) {
info!(
audit_logging_enabled = config.audit_logging.enabled,
audit_log_level = %config.audit_logging.log_level,
audit_async_logging = config.audit_logging.async_logging,
audit_buffer_size = config.audit_logging.buffer_size,
"Audit logging configuration"
);
info!(
error_sanitization_enabled = config.error_sanitization.enabled,
error_generic_messages = config.error_sanitization.generic_messages,
error_internal_logging = config.error_sanitization.internal_logging,
error_leak_sensitive = config.error_sanitization.leak_sensitive_details,
"Error sanitization configuration"
);
info!(
rate_limiting_enabled = config.rate_limiting.enabled,
auth_start_max = config.rate_limiting.auth_start_max_requests,
auth_callback_max = config.rate_limiting.auth_callback_max_requests,
auth_refresh_max = config.rate_limiting.auth_refresh_max_requests,
failed_login_max = config.rate_limiting.failed_login_max_requests,
"Rate limiting configuration"
);
info!(
state_encryption_enabled = config.state_encryption.enabled,
state_encryption_algorithm = %config.state_encryption.algorithm,
state_encryption_nonce_size = config.state_encryption.nonce_size,
state_encryption_key_size = config.state_encryption.key_size,
"State encryption configuration"
);
}
pub fn validate_security_config(config: &SecurityConfigFromSchema) -> Result<()> {
if config.error_sanitization.leak_sensitive_details {
warn!("SECURITY WARNING: leak_sensitive_details is enabled! This is a security risk.");
return Err(AuthError::ConfigError {
message: "leak_sensitive_details must be false in production".to_string(),
});
}
if config.rate_limiting.enabled {
if config.rate_limiting.auth_start_max_requests == 0 {
return Err(AuthError::ConfigError {
message: "auth_start_max_requests must be greater than 0".to_string(),
});
}
if config.rate_limiting.auth_start_window_secs == 0 {
return Err(AuthError::ConfigError {
message: "auth_start_window_secs must be greater than 0".to_string(),
});
}
}
if config.state_encryption.enabled && config.state_encryption.key_size != 32 {
warn!(
"State encryption key size is {} bytes, expected 32 bytes for ChaCha20-Poly1305",
config.state_encryption.key_size
);
}
info!("Security configuration validation passed");
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_init_default_security_config() {
let config = init_default_security_config();
assert!(config.audit_logging.enabled);
assert!(config.error_sanitization.enabled);
assert!(config.rate_limiting.enabled);
assert!(config.state_encryption.enabled);
}
#[test]
fn test_validate_security_config_success() {
let config = SecurityConfigFromSchema::default();
assert!(validate_security_config(&config).is_ok());
}
#[test]
fn test_validate_security_config_leak_sensitive_fails() {
let mut config = SecurityConfigFromSchema::default();
config.error_sanitization.leak_sensitive_details = true;
assert!(validate_security_config(&config).is_err());
}
#[test]
fn test_log_security_config() {
let config = SecurityConfigFromSchema::default();
log_security_config(&config);
}
#[test]
fn test_init_security_config_from_json() {
let json = serde_json::json!({
"security": {
"auditLogging": {
"enabled": true,
"logLevel": "debug"
},
"rateLimiting": {
"enabled": true,
"authStart": {
"maxRequests": 200,
"windowSecs": 60
}
}
}
});
let config = init_security_config_from_value(&json);
assert!(config.is_ok());
let cfg = config.unwrap();
assert_eq!(cfg.audit_logging.log_level, "debug");
assert_eq!(cfg.rate_limiting.auth_start_max_requests, 200);
}
#[test]
fn test_init_security_config_from_string() {
let json_str = r#"{
"security": {
"auditLogging": {
"enabled": true,
"logLevel": "info"
},
"errorSanitization": {
"enabled": true,
"genericMessages": true
}
}
}"#;
let config = init_security_config(json_str);
assert!(config.is_ok());
let cfg = config.unwrap();
assert_eq!(cfg.audit_logging.log_level, "info");
assert!(cfg.error_sanitization.generic_messages);
}
#[test]
fn test_init_security_config_missing_section() {
let json = serde_json::json!({});
let config = init_security_config_from_value(&json);
assert!(config.is_err());
}
}