use crate::config::config_runtime::{CorsConfig, RateLimitConfig, SameSitePolicy, SessionConfig};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use validator::{Validate, ValidationError};
#[derive(Debug, Clone, Serialize, Deserialize, Validate, Default)]
pub struct SecurityConfig {
pub auth_required: bool,
#[validate(nested)]
pub users: HashMap<String, UserConfig>,
#[validate(nested)]
pub jwt: Option<JwtConfig>,
#[validate(nested)]
pub oauth: Option<OAuthConfig>,
#[validate(nested)]
pub ldap: Option<LdapConfig>,
#[validate(nested)]
pub rate_limiting: Option<RateLimitConfig>,
pub cors: CorsConfig,
pub session: SessionConfig,
#[validate(nested)]
pub authentication: AuthenticationConfig,
#[validate(nested)]
pub api_keys: Option<ApiKeyConfig>,
#[validate(nested)]
pub certificate: Option<CertificateConfig>,
#[validate(nested)]
pub saml: Option<SamlConfig>,
#[validate(nested)]
pub rebac: Option<RebacConfig>,
#[validate(nested)]
pub mfa: Option<MfaConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate, Default)]
pub struct AuthenticationConfig {
pub enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct ApiKeyConfig {
pub enabled: bool,
#[validate(range(min = 1, max = 3650))] pub default_expiration_days: u32,
#[validate(range(min = 1, max = 100))]
pub max_keys_per_user: u32,
pub default_rate_limit: Option<ApiKeyRateLimit>,
pub usage_analytics: bool,
pub storage: ApiKeyStorageConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct ApiKeyRateLimit {
#[validate(range(min = 1))]
pub requests_per_minute: u32,
#[validate(range(min = 1))]
pub requests_per_hour: u32,
#[validate(range(min = 1))]
pub requests_per_day: u32,
#[validate(range(min = 1))]
pub burst_limit: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct ApiKeyStorageConfig {
pub backend: ApiKeyStorageBackend,
#[validate(length(min = 1))]
pub connection: String,
#[validate(length(min = 32))]
pub encryption_key: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ApiKeyStorageBackend {
Memory,
File,
Sqlite,
Postgres,
Redis,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct CertificateConfig {
pub enabled: bool,
pub require_client_cert: bool,
pub trust_store: Vec<PathBuf>,
pub crl_sources: Vec<String>,
pub check_crl: bool,
pub check_ocsp: bool,
pub allow_self_signed: bool,
pub user_mapping: CertificateUserMapping,
#[validate(range(min = 1, max = 10))]
pub max_chain_length: u8,
pub validation_level: CertificateValidationLevel,
pub trusted_issuers: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct CertificateUserMapping {
pub username_source: CertificateUsernameSource,
pub dn_mapping_rules: Vec<DnMappingRule>,
pub default_roles: Vec<String>,
pub ou_role_mapping: HashMap<String, Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CertificateUsernameSource {
CommonName,
SubjectDn,
EmailSan,
CustomPattern(String),
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct DnMappingRule {
#[validate(length(min = 1))]
pub pattern: String,
#[validate(length(min = 1))]
pub replacement: String,
pub roles: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CertificateValidationLevel {
Strict,
Moderate,
Permissive,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct SamlConfig {
pub enabled: bool,
#[validate(length(min = 1))]
pub sp_entity_id: String,
pub sp_cert_path: Option<PathBuf>,
pub sp_key_path: Option<PathBuf>,
pub idp: SamlIdpConfig,
#[validate(length(min = 1))]
pub acs_url: String,
pub slo_url: Option<String>,
pub attribute_mappings: SamlAttributeMappings,
#[validate(range(min = 300, max = 86400))]
pub session_timeout_secs: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct SamlIdpConfig {
#[validate(length(min = 1))]
pub entity_id: String,
#[validate(length(min = 1))]
pub sso_url: String,
pub slo_url: Option<String>,
#[validate(custom(function = "validate_path"))]
pub cert_path: PathBuf,
pub metadata_url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct SamlAttributeMappings {
#[validate(length(min = 1))]
pub username_attribute: String,
pub email_attribute: Option<String>,
pub name_attribute: Option<String>,
pub groups_attribute: Option<String>,
pub group_role_mapping: HashMap<String, Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct RebacConfig {
pub enabled: bool,
pub policy_mode: RebacPolicyMode,
pub storage: RebacStorageBackend,
pub openfga: Option<OpenFgaConfig>,
pub initial_relationships: Vec<RelationshipTupleConfig>,
pub audit_enabled: bool,
#[validate(range(min = 0, max = 3600))]
pub cache_ttl_secs: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum RebacPolicyMode {
RbacOnly,
RebacOnly,
#[default]
Combined,
Both,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum RebacStorageBackend {
#[default]
Memory,
OpenFga,
Rdf,
Database,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct OpenFgaConfig {
#[validate(length(min = 1))]
pub api_url: String,
#[validate(length(min = 1))]
pub store_id: String,
pub model_id: Option<String>,
pub api_token: Option<String>,
pub tls_enabled: bool,
#[validate(range(min = 1, max = 300))]
pub timeout_secs: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct RelationshipTupleConfig {
#[validate(length(min = 1))]
pub subject: String,
#[validate(length(min = 1))]
pub relation: String,
#[validate(length(min = 1))]
pub object: String,
pub condition: Option<RelationshipConditionConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum RelationshipConditionConfig {
TimeWindow {
not_before: Option<String>, not_after: Option<String>, },
IpAddress { allowed_ips: Vec<String> },
Attribute { key: String, value: String },
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct MfaConfig {
pub enabled: bool,
pub required: bool,
pub methods: MfaMethods,
#[validate(nested)]
pub totp: Option<TotpConfig>,
#[validate(nested)]
pub sms: Option<SmsConfig>,
#[validate(nested)]
pub email: Option<EmailConfig>,
#[validate(nested)]
pub webauthn: Option<WebAuthnConfig>,
pub backup_codes: BackupCodesConfig,
#[validate(range(min = 300, max = 86400))] pub session_duration_secs: u64,
pub storage_path: PathBuf,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MfaMethods {
pub totp: bool,
pub sms: bool,
pub email: bool,
pub webauthn: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct TotpConfig {
#[validate(length(min = 1))]
pub issuer: String,
#[validate(range(min = 6, max = 8))]
pub digits: u32,
#[validate(range(min = 15, max = 60))]
pub time_step_secs: u32,
#[validate(range(min = 0, max = 5))]
pub skew: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct SmsConfig {
pub provider: SmsProvider,
pub api_key: String,
pub sender: String,
#[validate(range(min = 60, max = 600))]
pub code_expiration_secs: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SmsProvider {
Twilio,
Aws,
Custom,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct EmailConfig {
#[validate(length(min = 1))]
pub smtp_server: String,
#[validate(range(min = 1, max = 65535))]
pub smtp_port: u16,
#[validate(email)]
pub from_address: String,
pub smtp_username: Option<String>,
pub smtp_password: Option<String>,
pub use_tls: bool,
#[validate(range(min = 60, max = 600))]
pub code_expiration_secs: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct WebAuthnConfig {
#[validate(length(min = 1))]
pub rp_id: String,
#[validate(length(min = 1))]
pub rp_name: String,
#[validate(url)]
pub origin: String,
pub require_resident_key: bool,
pub user_verification: UserVerificationRequirement,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum UserVerificationRequirement {
Required,
Preferred,
Discouraged,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct BackupCodesConfig {
pub enabled: bool,
#[validate(range(min = 5, max = 20))]
pub count: u32,
#[validate(range(min = 8, max = 16))]
pub length: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct JwtConfig {
#[validate(length(min = 32))]
pub secret: String,
#[validate(range(min = 300, max = 86400))] pub expiration_secs: u64,
#[validate(length(min = 1))]
pub issuer: String,
#[validate(length(min = 1))]
pub audience: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct OAuthConfig {
#[validate(length(min = 1))]
pub provider: String,
#[validate(length(min = 1))]
pub client_id: String,
#[validate(length(min = 1))]
pub client_secret: String,
#[validate(url)]
pub auth_url: String,
#[validate(url)]
pub token_url: String,
#[validate(url)]
pub user_info_url: String,
pub scopes: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct LdapConfig {
#[validate(url)]
pub server: String,
#[validate(length(min = 1))]
pub bind_dn: String,
#[validate(length(min = 1))]
pub bind_password: String,
#[validate(length(min = 1))]
pub user_base_dn: String,
#[validate(length(min = 1))]
pub user_filter: String,
#[validate(length(min = 1))]
pub group_base_dn: String,
#[validate(length(min = 1))]
pub group_filter: String,
pub use_tls: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct UserConfig {
#[validate(length(min = 1))]
pub password_hash: String,
pub roles: Vec<String>,
pub permissions: Vec<crate::auth::types::Permission>,
pub enabled: bool,
pub email: Option<String>,
pub full_name: Option<String>,
pub last_login: Option<chrono::DateTime<chrono::Utc>>,
pub failed_login_attempts: u32,
pub locked_until: Option<chrono::DateTime<chrono::Utc>>,
}
fn validate_path(path: &std::path::Path) -> Result<(), ValidationError> {
if path.as_os_str().is_empty() {
return Err(ValidationError::new("path_empty"));
}
Ok(())
}