use serde::{Deserialize, Serialize};
use super::session::SessionHandleConfig;
fn default_argon2_memory_kib() -> u32 {
19_456
}
fn default_argon2_time_cost() -> u32 {
2
}
fn default_argon2_parallelism() -> u32 {
1
}
fn default_argon2_output_len() -> usize {
32
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Argon2Config {
#[serde(default = "default_argon2_memory_kib")]
pub memory_kib: u32,
#[serde(default = "default_argon2_time_cost")]
pub time_cost: u32,
#[serde(default = "default_argon2_parallelism")]
pub parallelism: u32,
#[serde(default = "default_argon2_output_len")]
pub output_len: usize,
}
impl Default for Argon2Config {
fn default() -> Self {
Self {
memory_kib: default_argon2_memory_kib(),
time_cost: default_argon2_time_cost(),
parallelism: default_argon2_parallelism(),
output_len: default_argon2_output_len(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AuthMode {
Trust,
Password,
Certificate,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JwtAuthConfig {
#[serde(default = "default_jwks_refresh")]
pub jwks_refresh_secs: u64,
#[serde(default = "default_jwks_min_refetch")]
pub jwks_min_refetch_secs: u64,
#[serde(default = "default_allowed_algorithms")]
pub allowed_algorithms: Vec<String>,
#[serde(default = "default_clock_skew")]
pub clock_skew_secs: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub jwks_cache_path: Option<String>,
#[serde(default)]
pub providers: Vec<JwtProviderConfig>,
#[serde(default)]
pub jit_provisioning: bool,
#[serde(default = "default_true")]
pub jit_sync_claims: bool,
#[serde(default)]
pub claims: std::collections::HashMap<String, String>,
#[serde(default)]
pub status_claim: Option<String>,
#[serde(default)]
pub blocked_statuses: Vec<String>,
#[serde(default)]
pub enforce_scopes: bool,
#[serde(default)]
pub allow_http_jwks: bool,
#[serde(default)]
pub allow_jwks_hosts: Vec<String>,
#[serde(default)]
pub allow_jwks_cidrs: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JwtProviderConfig {
pub name: String,
pub jwks_url: String,
#[serde(default)]
pub issuer: String,
#[serde(default)]
pub audience: String,
}
impl JwtProviderConfig {
pub fn validate(
&self,
policy: &crate::control::security::jwks::url::JwksPolicy,
) -> crate::Result<()> {
if self.name.trim().is_empty() {
return Err(crate::Error::Config {
detail: "auth.jwt provider must have a non-empty name".into(),
});
}
if self.issuer.trim().is_empty() {
return Err(crate::Error::Config {
detail: format!(
"auth.jwt provider '{}' must set a non-empty `issuer`; \
empty issuer would disable issuer validation and allow \
cross-tenant token acceptance",
self.name
),
});
}
policy
.check_url(&self.jwks_url)
.map_err(|e| crate::Error::Config {
detail: format!("auth.jwt provider '{}' has unsafe jwks_url: {e}", self.name),
})?;
Ok(())
}
}
impl JwtAuthConfig {
pub fn jwks_policy(
&self,
) -> Result<
crate::control::security::jwks::url::JwksPolicy,
crate::control::security::jwks::url::UrlValidationError,
> {
crate::control::security::jwks::url::JwksPolicy::from_parts(
self.allow_http_jwks,
&self.allow_jwks_hosts,
&self.allow_jwks_cidrs,
)
}
pub fn validate(&self) -> crate::Result<()> {
let policy = self.jwks_policy().map_err(|e| crate::Error::Config {
detail: format!("auth.jwt allow-list is invalid: {e}"),
})?;
for p in &self.providers {
p.validate(&policy)?;
}
Ok(())
}
}
fn default_jwks_refresh() -> u64 {
3600
}
fn default_jwks_min_refetch() -> u64 {
60
}
fn default_clock_skew() -> u64 {
60
}
fn default_allowed_algorithms() -> Vec<String> {
vec!["RS256".into(), "ES256".into()]
}
fn default_true() -> bool {
true
}
impl Default for JwtAuthConfig {
fn default() -> Self {
Self {
jwks_refresh_secs: default_jwks_refresh(),
jwks_min_refetch_secs: default_jwks_min_refetch(),
allowed_algorithms: default_allowed_algorithms(),
clock_skew_secs: default_clock_skew(),
jwks_cache_path: None,
providers: Vec::new(),
jit_provisioning: false,
jit_sync_claims: true,
claims: std::collections::HashMap::new(),
status_claim: None,
blocked_statuses: Vec::new(),
enforce_scopes: false,
allow_http_jwks: false,
allow_jwks_hosts: Vec::new(),
allow_jwks_cidrs: Vec::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthConfig {
pub mode: AuthMode,
pub superuser_name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub superuser_password: Option<String>,
pub min_password_length: usize,
pub max_failed_logins: u32,
pub lockout_duration_secs: u64,
pub idle_timeout_secs: u64,
#[serde(default)]
pub session_absolute_timeout_secs: u64,
pub max_connections_per_user: u32,
pub password_expiry_days: u32,
#[serde(default)]
pub password_expiry_grace_days: u32,
pub audit_retention_days: u32,
#[serde(default)]
pub audit_max_entries: u64,
#[serde(default)]
pub argon2: Argon2Config,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub jwt: Option<JwtAuthConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rate_limit: Option<crate::control::security::ratelimit::config::RateLimitConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metering: Option<crate::control::security::metering::config::MeteringConfig>,
#[serde(default)]
pub session: SessionHandleConfig,
}
impl Default for AuthConfig {
fn default() -> Self {
Self {
mode: AuthMode::Password,
superuser_name: "nodedb".into(),
superuser_password: None,
min_password_length: 8,
max_failed_logins: 5,
lockout_duration_secs: 300,
idle_timeout_secs: 3600,
session_absolute_timeout_secs: 0,
max_connections_per_user: 0,
password_expiry_days: 0,
password_expiry_grace_days: 0,
audit_retention_days: 0,
audit_max_entries: 0,
argon2: Argon2Config::default(),
jwt: None,
rate_limit: None,
metering: None,
session: SessionHandleConfig::default(),
}
}
}