Skip to main content

fraiseql_auth/
security_init.rs

1//! Security system initialization from compiled schema configuration
2//!
3//! Loads security configuration from schema.compiled.json and initializes
4//! all security subsystems with proper environment variable overrides.
5
6use serde_json::Value as JsonValue;
7use tracing::{debug, info, warn};
8
9use super::security_config::SecurityConfigFromSchema;
10use crate::{AuthError, error::Result};
11
12/// Maximum size for schema JSON to prevent DOS attacks
13/// 10 MB should be more than sufficient for any realistic schema
14const MAX_SCHEMA_JSON_SIZE: usize = 10 * 1024 * 1024; // 10 MB
15
16/// Maximum size for security configuration section
17/// Security config should be < 100 KB in realistic deployments
18const MAX_SECURITY_CONFIG_SIZE: usize = 100 * 1024; // 100 KB
19
20/// Initialize security configuration from compiled schema JSON string
21///
22/// Loads security settings from the schema.compiled.json and applies
23/// environment variable overrides. This function should be called during
24/// server startup after loading the compiled schema.
25///
26/// # SECURITY
27/// - Validates schema JSON size to prevent DOS attacks
28/// - Rejects JSON > 10 MB
29/// - Rejects security config section > 100 KB
30///
31/// # Arguments
32///
33/// * `schema_json_str` - The compiled schema as a JSON string
34///
35/// # Returns
36///
37/// Returns a configured `SecurityConfigFromSchema` with environment overrides applied
38///
39/// # Errors
40///
41/// Returns error if:
42/// - JSON size exceeds limits
43/// - JSON parsing fails
44/// - Security configuration section is invalid or missing required fields
45///
46/// # Example
47///
48/// ```no_run
49/// // Requires: compiled schema JSON string loaded from disk or a schema loader.
50/// # fn example() -> fraiseql_auth::error::Result<()> {
51/// use fraiseql_auth::security_init::init_security_config;
52/// let json_str = r#"{"security":{"auditLogging":{"enabled":true}}}"#;
53/// let security_config = init_security_config(json_str)?;
54/// # Ok(())
55/// # }
56/// ```
57pub fn init_security_config(schema_json_str: &str) -> Result<SecurityConfigFromSchema> {
58    debug!("Parsing schema JSON for security configuration");
59
60    // SECURITY: Check JSON size to prevent DOS from oversized payloads
61    if schema_json_str.len() > MAX_SCHEMA_JSON_SIZE {
62        warn!(
63            "Schema JSON exceeds maximum size: {} > {} bytes",
64            schema_json_str.len(),
65            MAX_SCHEMA_JSON_SIZE
66        );
67        return Err(AuthError::ConfigError {
68            message: format!("Schema JSON exceeds maximum size of {} bytes", MAX_SCHEMA_JSON_SIZE),
69        });
70    }
71
72    // Parse JSON string to JsonValue
73    let schema_json: JsonValue = serde_json::from_str(schema_json_str).map_err(|e| {
74        warn!("Failed to parse schema JSON: {e}");
75        AuthError::ConfigError {
76            message: format!("Invalid schema JSON: {e}"),
77        }
78    })?;
79
80    init_security_config_from_value(&schema_json)
81}
82
83/// Initialize security configuration from compiled schema JSON value
84///
85/// Internal function that works with parsed JsonValue. Use `init_security_config` for strings.
86///
87/// # SECURITY
88/// - Validates security configuration size to prevent DOS attacks
89/// - Rejects security config > 100 KB
90///
91/// # Arguments
92///
93/// * `schema_json` - The compiled schema as a JsonValue
94///
95/// # Returns
96///
97/// Returns a configured `SecurityConfigFromSchema` with environment overrides applied
98///
99/// # Errors
100///
101/// Returns error if security configuration section is invalid or missing required fields
102pub(crate) fn init_security_config_from_value(
103    schema_json: &JsonValue,
104) -> Result<SecurityConfigFromSchema> {
105    debug!("Initializing security configuration from schema");
106
107    // Extract security section from schema
108    let security_value = schema_json.get("security").ok_or_else(|| {
109        warn!("No security configuration found in schema, using defaults");
110        AuthError::ConfigError {
111            message: "Missing security configuration in schema".to_string(),
112        }
113    })?;
114
115    // SECURITY: Check security config size to prevent DOS
116    let security_json_str = security_value.to_string();
117    if security_json_str.len() > MAX_SECURITY_CONFIG_SIZE {
118        warn!(
119            "Security configuration exceeds maximum size: {} > {} bytes",
120            security_json_str.len(),
121            MAX_SECURITY_CONFIG_SIZE
122        );
123        return Err(AuthError::ConfigError {
124            message: format!(
125                "Security configuration exceeds maximum size of {} bytes",
126                MAX_SECURITY_CONFIG_SIZE
127            ),
128        });
129    }
130
131    // Parse security configuration from schema
132    let mut config = SecurityConfigFromSchema::from_json(security_value).map_err(|e| {
133        warn!("Failed to parse security configuration: {e}");
134        AuthError::ConfigError {
135            message: format!("Invalid security configuration: {e}"),
136        }
137    })?;
138
139    info!("Security configuration loaded from schema");
140
141    // Apply environment variable overrides
142    config.apply_env_overrides();
143    debug!("Security environment variable overrides applied");
144
145    Ok(config)
146}
147
148/// Initialize security configuration with default values if schema doesn't have security config
149///
150/// This is useful for backward compatibility when the schema doesn't include
151/// a security section. It loads defaults and applies environment overrides.
152///
153/// # Returns
154///
155/// A default `SecurityConfigFromSchema` with environment overrides applied
156pub fn init_default_security_config() -> SecurityConfigFromSchema {
157    info!("Initializing default security configuration");
158    let mut config = SecurityConfigFromSchema::default();
159    config.apply_env_overrides();
160    debug!("Default security configuration applied with environment overrides");
161    config
162}
163
164/// Log the active security configuration (sanitized for safe logging)
165///
166/// Outputs current security settings to logs, excluding sensitive values
167/// like encryption keys.
168///
169/// # Arguments
170///
171/// * `config` - The security configuration to log
172pub fn log_security_config(config: &SecurityConfigFromSchema) {
173    info!(
174        audit_logging_enabled = config.audit_logging.enabled,
175        audit_log_level = %config.audit_logging.log_level,
176        audit_async_logging = config.audit_logging.async_logging,
177        audit_buffer_size = config.audit_logging.buffer_size,
178        "Audit logging configuration"
179    );
180
181    info!(
182        error_sanitization_enabled = config.error_sanitization.enabled,
183        error_generic_messages = config.error_sanitization.generic_messages,
184        error_internal_logging = config.error_sanitization.internal_logging,
185        error_leak_sensitive = config.error_sanitization.leak_sensitive_details,
186        "Error sanitization configuration"
187    );
188
189    info!(
190        rate_limiting_enabled = config.rate_limiting.enabled,
191        auth_start_max = config.rate_limiting.auth_start_max_requests,
192        auth_callback_max = config.rate_limiting.auth_callback_max_requests,
193        auth_refresh_max = config.rate_limiting.auth_refresh_max_requests,
194        failed_login_max = config.rate_limiting.failed_login_max_requests,
195        "Rate limiting configuration"
196    );
197
198    info!(
199        state_encryption_enabled = config.state_encryption.enabled,
200        state_encryption_algorithm = %config.state_encryption.algorithm,
201        state_encryption_nonce_size = config.state_encryption.nonce_size,
202        state_encryption_key_size = config.state_encryption.key_size,
203        "State encryption configuration"
204    );
205}
206
207/// Verify security configuration consistency.
208///
209/// Performs validation checks to ensure the loaded security configuration
210/// doesn't have dangerous or conflicting settings.
211///
212/// # Arguments
213///
214/// * `config` - The security configuration to validate
215///
216/// # Returns
217///
218/// Returns `Ok(())` if configuration is valid.
219///
220/// # Errors
221///
222/// Returns [`AuthError::ConfigError`] if `leak_sensitive_details` is `true`,
223/// `auth_start_max_requests` is zero when rate limiting is enabled, or
224/// `auth_start_window_secs` is zero when rate limiting is enabled.
225pub fn validate_security_config(config: &SecurityConfigFromSchema) -> Result<()> {
226    // Check if sensitive data leaking is disabled (security requirement)
227    if config.error_sanitization.leak_sensitive_details {
228        warn!("SECURITY WARNING: leak_sensitive_details is enabled! This is a security risk.");
229        return Err(AuthError::ConfigError {
230            message: "leak_sensitive_details must be false in production".to_string(),
231        });
232    }
233
234    // Check rate limits are reasonable
235    if config.rate_limiting.enabled {
236        if config.rate_limiting.auth_start_max_requests == 0 {
237            return Err(AuthError::ConfigError {
238                message: "auth_start_max_requests must be greater than 0".to_string(),
239            });
240        }
241        if config.rate_limiting.auth_start_window_secs == 0 {
242            return Err(AuthError::ConfigError {
243                message: "auth_start_window_secs must be greater than 0".to_string(),
244            });
245        }
246    }
247
248    // Check state encryption key size if enabled
249    if config.state_encryption.enabled && config.state_encryption.key_size != 32 {
250        warn!(
251            "State encryption key size is {} bytes, expected 32 bytes for ChaCha20-Poly1305",
252            config.state_encryption.key_size
253        );
254    }
255
256    info!("Security configuration validation passed");
257    Ok(())
258}