use std::fmt;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct SecuritySettings {
pub default_policy: Option<String>,
pub rules: Vec<AuthorizationRule>,
pub policies: Vec<AuthorizationPolicy>,
pub field_auth: Vec<FieldAuthRule>,
pub enterprise: EnterpriseSecurityConfig,
pub error_sanitization: Option<ErrorSanitizationTomlConfig>,
pub rate_limiting: Option<RateLimitingSecurityConfig>,
pub state_encryption: Option<StateEncryptionConfig>,
pub pkce: Option<PkceConfig>,
pub api_keys: Option<ApiKeySecurityConfig>,
pub token_revocation: Option<TokenRevocationSecurityConfig>,
pub trusted_documents: Option<TrustedDocumentsConfig>,
}
impl Default for SecuritySettings {
fn default() -> Self {
Self {
default_policy: Some("authenticated".to_string()),
rules: vec![],
policies: vec![],
field_auth: vec![],
enterprise: EnterpriseSecurityConfig::default(),
error_sanitization: None,
rate_limiting: None,
state_encryption: None,
pkce: None,
api_keys: None,
token_revocation: None,
trusted_documents: None,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct AuthorizationRule {
pub name: String,
pub rule: String,
pub description: Option<String>,
#[serde(default)]
pub cacheable: bool,
pub cache_ttl_seconds: Option<u32>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct AuthorizationPolicy {
pub name: String,
#[serde(rename = "type")]
pub policy_type: String,
pub rule: Option<String>,
pub roles: Vec<String>,
pub strategy: Option<String>,
#[serde(default)]
pub attributes: Vec<String>,
pub description: Option<String>,
pub cache_ttl_seconds: Option<u32>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FieldAuthRule {
pub type_name: String,
pub field_name: String,
pub policy: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct EnterpriseSecurityConfig {
pub rate_limiting_enabled: bool,
pub auth_endpoint_max_requests: u32,
pub auth_endpoint_window_seconds: u64,
pub audit_logging_enabled: bool,
pub audit_log_backend: String,
pub audit_retention_days: u32,
pub error_sanitization: bool,
pub hide_implementation_details: bool,
pub constant_time_comparison: bool,
pub pkce_enabled: bool,
}
impl Default for EnterpriseSecurityConfig {
fn default() -> Self {
Self {
rate_limiting_enabled: true,
auth_endpoint_max_requests: 100,
auth_endpoint_window_seconds: 60,
audit_logging_enabled: true,
audit_log_backend: "postgresql".to_string(),
audit_retention_days: 365,
error_sanitization: true,
hide_implementation_details: true,
constant_time_comparison: true,
pkce_enabled: true,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ErrorSanitizationTomlConfig {
pub enabled: bool,
#[serde(default = "default_true")]
pub hide_implementation_details: bool,
#[serde(default = "default_true")]
pub sanitize_database_errors: bool,
pub custom_error_message: Option<String>,
}
impl Default for ErrorSanitizationTomlConfig {
fn default() -> Self {
Self {
enabled: false,
hide_implementation_details: true,
sanitize_database_errors: true,
custom_error_message: None,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct RateLimitingSecurityConfig {
pub enabled: bool,
pub requests_per_second: u32,
pub burst_size: u32,
pub auth_start_max_requests: u32,
pub auth_start_window_secs: u64,
pub auth_callback_max_requests: u32,
pub auth_callback_window_secs: u64,
pub auth_refresh_max_requests: u32,
pub auth_refresh_window_secs: u64,
pub auth_logout_max_requests: u32,
pub auth_logout_window_secs: u64,
pub failed_login_max_attempts: u32,
pub failed_login_lockout_secs: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub requests_per_second_per_user: Option<u32>,
pub redis_url: Option<String>,
#[serde(default)]
pub trust_proxy_headers: bool,
}
impl Default for RateLimitingSecurityConfig {
fn default() -> Self {
Self {
enabled: false,
requests_per_second: 100,
requests_per_second_per_user: None,
burst_size: 200,
auth_start_max_requests: 5,
auth_start_window_secs: 60,
auth_callback_max_requests: 10,
auth_callback_window_secs: 60,
auth_refresh_max_requests: 20,
auth_refresh_window_secs: 300,
auth_logout_max_requests: 30,
auth_logout_window_secs: 60,
failed_login_max_attempts: 10,
failed_login_lockout_secs: 900,
redis_url: None,
trust_proxy_headers: false,
}
}
}
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
#[non_exhaustive]
pub enum EncryptionAlgorithm {
#[default]
#[serde(rename = "chacha20-poly1305")]
Chacha20Poly1305,
#[serde(rename = "aes-256-gcm")]
Aes256Gcm,
}
impl fmt::Display for EncryptionAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Chacha20Poly1305 => f.write_str("chacha20-poly1305"),
Self::Aes256Gcm => f.write_str("aes-256-gcm"),
}
}
}
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum KeySource {
#[default]
Env,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct StateEncryptionConfig {
pub enabled: bool,
pub algorithm: EncryptionAlgorithm,
pub key_source: KeySource,
pub key_env: Option<String>,
}
impl Default for StateEncryptionConfig {
fn default() -> Self {
Self {
enabled: false,
algorithm: EncryptionAlgorithm::default(),
key_source: KeySource::Env,
key_env: Some("STATE_ENCRYPTION_KEY".to_string()),
}
}
}
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
#[non_exhaustive]
pub enum CodeChallengeMethod {
#[default]
#[serde(rename = "S256")]
S256,
#[serde(rename = "plain")]
Plain,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct PkceConfig {
pub enabled: bool,
pub code_challenge_method: CodeChallengeMethod,
pub state_ttl_secs: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub redis_url: Option<String>,
}
impl Default for PkceConfig {
fn default() -> Self {
Self {
enabled: false,
code_challenge_method: CodeChallengeMethod::S256,
state_ttl_secs: 600,
redis_url: None,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ApiKeySecurityConfig {
pub enabled: bool,
pub header: String,
pub hash_algorithm: String,
pub storage: String,
#[serde(default, rename = "static")]
pub static_keys: Vec<StaticApiKeyEntry>,
}
impl Default for ApiKeySecurityConfig {
fn default() -> Self {
Self {
enabled: false,
header: "X-API-Key".to_string(),
hash_algorithm: "sha256".to_string(),
storage: "env".to_string(),
static_keys: vec![],
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct StaticApiKeyEntry {
pub key_hash: String,
#[serde(default)]
pub scopes: Vec<String>,
pub name: String,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum TrustedDocumentMode {
Strict,
#[default]
Permissive,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct TrustedDocumentsConfig {
pub enabled: bool,
pub mode: TrustedDocumentMode,
#[serde(skip_serializing_if = "Option::is_none")]
pub manifest_path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub manifest_url: Option<String>,
#[serde(default)]
pub reload_interval_secs: u64,
}
impl Default for TrustedDocumentsConfig {
fn default() -> Self {
Self {
enabled: false,
mode: TrustedDocumentMode::Permissive,
manifest_path: None,
manifest_url: None,
reload_interval_secs: 0,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct TokenRevocationSecurityConfig {
pub enabled: bool,
pub backend: String,
#[serde(default = "default_true")]
pub require_jti: bool,
#[serde(default)]
pub fail_open: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub redis_url: Option<String>,
}
impl Default for TokenRevocationSecurityConfig {
fn default() -> Self {
Self {
enabled: false,
backend: "memory".to_string(),
require_jti: true,
fail_open: false,
redis_url: None,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct OidcClientConfig {
pub discovery_url: String,
pub client_id: String,
pub client_secret_env: String,
pub server_redirect_uri: String,
}
fn default_true() -> bool {
true
}