pub mod app_config;
pub mod config_manager;
pub use app_config::{AppConfig, ConfigBuilder as AppConfigBuilder};
pub use config_manager::{
AuthFrameworkSettings, ConfigBuilder as LayeredConfigBuilder, ConfigIntegration, ConfigManager,
SessionCookieSettings, SessionSettings,
};
use crate::errors::{AuthError, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::Duration;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthConfig {
pub token_lifetime: Duration,
pub refresh_token_lifetime: Duration,
pub enable_multi_factor: bool,
pub issuer: String,
pub audience: String,
pub secret: Option<String>,
pub storage: StorageConfig,
pub rate_limiting: RateLimitConfig,
pub security: SecurityConfig,
#[serde(default)]
pub cors: CorsConfig,
pub audit: AuditConfig,
#[serde(default)]
pub enable_caching: bool,
#[serde(default = "default_max_failed_attempts")]
pub max_failed_attempts: u32,
#[serde(default)]
pub enable_rbac: bool,
#[serde(default)]
pub enable_middleware: bool,
pub method_configs: HashMap<String, serde_json::Value>,
#[serde(default)]
pub force_production_mode: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum StorageConfig {
Memory,
#[cfg(feature = "redis-storage")]
Redis { url: String, key_prefix: String },
#[cfg(feature = "postgres-storage")]
Postgres {
connection_string: String,
table_prefix: String,
},
#[cfg(feature = "mysql-storage")]
MySQL {
connection_string: String,
table_prefix: String,
},
#[cfg(feature = "sqlite-storage")]
Sqlite { connection_string: String },
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RateLimitConfig {
pub enabled: bool,
pub max_requests: u32,
pub window: Duration,
pub burst: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityConfig {
pub min_password_length: usize,
pub require_password_complexity: bool,
pub password_hash_algorithm: PasswordHashAlgorithm,
pub jwt_algorithm: JwtAlgorithm,
pub secret_key: Option<String>,
pub previous_secret_key: Option<String>,
pub secure_cookies: bool,
pub cookie_same_site: CookieSameSite,
pub csrf_protection: bool,
pub session_timeout: Duration,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PasswordHashAlgorithm {
Argon2,
Bcrypt,
Scrypt,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum JwtAlgorithm {
HS256,
HS384,
HS512,
RS256,
RS384,
RS512,
ES256,
ES384,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CookieSameSite {
Strict,
Lax,
None,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CorsConfig {
pub enabled: bool,
pub allowed_origins: Vec<String>,
pub allowed_methods: Vec<String>,
pub allowed_headers: Vec<String>,
pub max_age_secs: u32,
}
impl Default for CorsConfig {
fn default() -> Self {
Self {
enabled: false,
allowed_origins: Vec::new(),
allowed_methods: vec![
"GET".to_string(),
"POST".to_string(),
"PUT".to_string(),
"DELETE".to_string(),
"OPTIONS".to_string(),
],
allowed_headers: vec![
"Authorization".to_string(),
"Content-Type".to_string(),
"Accept".to_string(),
],
max_age_secs: 3600,
}
}
}
impl CorsConfig {
pub fn for_origins<I, S>(origins: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self {
enabled: true,
allowed_origins: origins.into_iter().map(Into::into).collect(),
..Default::default()
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditConfig {
pub enabled: bool,
pub log_success: bool,
pub log_failures: bool,
pub log_permissions: bool,
pub log_tokens: bool,
pub storage: AuditStorage,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AuditStorage {
Tracing,
File { path: String },
Database { connection_string: String },
External { endpoint: String, api_key: String },
}
const fn default_max_failed_attempts() -> u32 {
5
}
impl Default for AuthConfig {
fn default() -> Self {
Self {
token_lifetime: Duration::from_secs(3600), refresh_token_lifetime: Duration::from_secs(86400 * 7), enable_multi_factor: false,
issuer: "auth-framework".to_string(),
audience: "api".to_string(),
secret: None,
storage: StorageConfig::Memory,
rate_limiting: RateLimitConfig::default(),
security: SecurityConfig::default(),
cors: CorsConfig::default(),
audit: AuditConfig::default(),
enable_caching: false,
max_failed_attempts: default_max_failed_attempts(),
enable_rbac: false,
enable_middleware: false,
method_configs: HashMap::new(),
force_production_mode: false,
}
}
}
impl Default for RateLimitConfig {
fn default() -> Self {
Self {
enabled: true,
max_requests: 100,
window: Duration::from_secs(60), burst: 10,
}
}
}
impl Default for SecurityConfig {
fn default() -> Self {
Self {
min_password_length: 8,
require_password_complexity: true,
password_hash_algorithm: PasswordHashAlgorithm::Argon2,
jwt_algorithm: JwtAlgorithm::HS256,
secret_key: None,
previous_secret_key: None,
secure_cookies: true,
cookie_same_site: CookieSameSite::Lax,
csrf_protection: true,
session_timeout: Duration::from_secs(3600 * 24), }
}
}
impl Default for AuditConfig {
fn default() -> Self {
Self {
enabled: true,
log_success: true,
log_failures: true,
log_permissions: true,
log_tokens: false, storage: AuditStorage::Tracing,
}
}
}
impl std::fmt::Display for AuthConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"AuthConfig {{ tokens: {}s/{}s refresh, storage: {}, mfa: {}, rbac: {}, rate_limit: {}, security: {}, cors: {}, audit: {} }}",
self.token_lifetime.as_secs(),
self.refresh_token_lifetime.as_secs(),
self.storage,
if self.enable_multi_factor { "on" } else { "off" },
if self.enable_rbac { "on" } else { "off" },
self.rate_limiting,
self.security,
self.cors,
self.audit,
)
}
}
impl std::fmt::Display for StorageConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Memory => write!(f, "memory"),
#[cfg(feature = "redis-storage")]
Self::Redis { url, .. } => write!(f, "redis({})", url),
#[cfg(feature = "postgres-storage")]
Self::Postgres { .. } => write!(f, "postgres"),
#[cfg(feature = "mysql-storage")]
Self::MySQL { .. } => write!(f, "mysql"),
#[cfg(feature = "sqlite-storage")]
Self::Sqlite { .. } => write!(f, "sqlite"),
Self::Custom(name) => write!(f, "custom({})", name),
}
}
}
impl std::fmt::Display for SecurityConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Security {{ pw≥{}, hash: {}, jwt: {}, cookies: {}, csrf: {}, session: {}s }}",
self.min_password_length,
self.password_hash_algorithm,
self.jwt_algorithm,
if self.secure_cookies { "secure" } else { "plain" },
if self.csrf_protection { "on" } else { "off" },
self.session_timeout.as_secs(),
)
}
}
impl std::fmt::Display for PasswordHashAlgorithm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Argon2 => write!(f, "argon2id"),
Self::Bcrypt => write!(f, "bcrypt"),
Self::Scrypt => write!(f, "scrypt"),
}
}
}
impl std::fmt::Display for JwtAlgorithm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::HS256 => write!(f, "HS256"),
Self::HS384 => write!(f, "HS384"),
Self::HS512 => write!(f, "HS512"),
Self::RS256 => write!(f, "RS256"),
Self::RS384 => write!(f, "RS384"),
Self::RS512 => write!(f, "RS512"),
Self::ES256 => write!(f, "ES256"),
Self::ES384 => write!(f, "ES384"),
}
}
}
impl std::fmt::Display for RateLimitConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.enabled {
write!(
f,
"{} req/{}s (burst {})",
self.max_requests,
self.window.as_secs(),
self.burst,
)
} else {
write!(f, "disabled")
}
}
}
impl std::fmt::Display for CorsConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.enabled {
if self.allowed_origins.is_empty() {
write!(f, "cors(no origins)")
} else {
write!(f, "cors({})", self.allowed_origins.join(", "))
}
} else {
write!(f, "cors(off)")
}
}
}
impl std::fmt::Display for AuditConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.enabled {
write!(f, "audit({})", self.storage)
} else {
write!(f, "audit(off)")
}
}
}
impl std::fmt::Display for AuditStorage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Tracing => write!(f, "tracing"),
Self::File { path } => write!(f, "file:{}", path),
Self::Database { .. } => write!(f, "database"),
Self::External { endpoint, .. } => write!(f, "external:{}", endpoint),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuntimeConfig {
pub token_lifetime_secs: u64,
pub refresh_token_lifetime_secs: u64,
pub enable_multi_factor: bool,
pub rate_limiting_enabled: bool,
pub rate_limit_max_requests: u32,
pub rate_limit_window_secs: u64,
pub rate_limit_burst: u32,
pub min_password_length: usize,
pub require_password_complexity: bool,
pub secure_cookies: bool,
pub csrf_protection: bool,
pub session_timeout_secs: u64,
pub audit_enabled: bool,
pub audit_log_success: bool,
pub audit_log_failures: bool,
pub audit_log_permissions: bool,
pub audit_log_tokens: bool,
}
impl RuntimeConfig {
pub fn from_auth_config(cfg: &AuthConfig) -> Self {
Self {
token_lifetime_secs: cfg.token_lifetime.as_secs(),
refresh_token_lifetime_secs: cfg.refresh_token_lifetime.as_secs(),
enable_multi_factor: cfg.enable_multi_factor,
rate_limiting_enabled: cfg.rate_limiting.enabled,
rate_limit_max_requests: cfg.rate_limiting.max_requests,
rate_limit_window_secs: cfg.rate_limiting.window.as_secs(),
rate_limit_burst: cfg.rate_limiting.burst,
min_password_length: cfg.security.min_password_length,
require_password_complexity: cfg.security.require_password_complexity,
secure_cookies: cfg.security.secure_cookies,
csrf_protection: cfg.security.csrf_protection,
session_timeout_secs: cfg.security.session_timeout.as_secs(),
audit_enabled: cfg.audit.enabled,
audit_log_success: cfg.audit.log_success,
audit_log_failures: cfg.audit.log_failures,
audit_log_permissions: cfg.audit.log_permissions,
audit_log_tokens: cfg.audit.log_tokens,
}
}
}
impl Default for RuntimeConfig {
fn default() -> Self {
Self::from_auth_config(&AuthConfig::default())
}
}
impl AuthConfig {
pub fn new() -> Self {
Self::default()
}
pub fn from_env() -> Self {
let mut config = Self::default();
if let Ok(secret) = std::env::var("JWT_SECRET") {
config.secret = Some(secret.clone());
config.security.secret_key = Some(secret);
}
if let Ok(issuer) = std::env::var("AUTH_ISSUER") {
config.issuer = issuer;
}
if let Ok(audience) = std::env::var("AUTH_AUDIENCE") {
config.audience = audience;
}
#[cfg(feature = "postgres-storage")]
if let Ok(url) = std::env::var("DATABASE_URL") {
config.storage = StorageConfig::Postgres {
connection_string: url,
table_prefix: "auth_".to_string(),
};
}
#[cfg(feature = "redis-storage")]
if matches!(config.storage, StorageConfig::Memory) {
if let Ok(url) = std::env::var("REDIS_URL") {
config.storage = StorageConfig::Redis {
url,
key_prefix: "auth:".to_string(),
};
}
}
config
}
pub fn builder() -> crate::builders::AuthBuilder {
crate::builders::AuthBuilder::new()
}
pub fn token_lifetime(mut self, lifetime: Duration) -> Self {
self.token_lifetime = lifetime;
self
}
pub fn refresh_token_lifetime(mut self, lifetime: Duration) -> Self {
self.refresh_token_lifetime = lifetime;
self
}
pub fn enable_multi_factor(mut self, enabled: bool) -> Self {
self.enable_multi_factor = enabled;
self
}
pub fn issuer(mut self, issuer: impl Into<String>) -> Self {
self.issuer = issuer.into();
self
}
pub fn audience(mut self, audience: impl Into<String>) -> Self {
self.audience = audience.into();
self
}
pub fn secret(mut self, secret: impl Into<String>) -> Self {
self.secret = Some(secret.into());
self
}
pub fn require_mfa(mut self, required: bool) -> Self {
self.enable_multi_factor = required;
self
}
pub fn enable_caching(mut self, enabled: bool) -> Self {
self.enable_caching = enabled;
self
}
pub fn max_failed_attempts(mut self, max: u32) -> Self {
self.max_failed_attempts = max;
self
}
pub fn enable_rbac(mut self, enabled: bool) -> Self {
self.enable_rbac = enabled;
self
}
pub fn enable_security_audit(mut self, enabled: bool) -> Self {
self.audit.enabled = enabled;
self
}
pub fn enable_middleware(mut self, enabled: bool) -> Self {
self.enable_middleware = enabled;
self
}
pub fn force_production_mode(mut self) -> Self {
self.force_production_mode = true;
self
}
pub fn storage(mut self, storage: StorageConfig) -> Self {
self.storage = storage;
self
}
#[cfg(feature = "redis-storage")]
pub fn redis_storage(mut self, url: impl Into<String>) -> Self {
self.storage = StorageConfig::Redis {
url: url.into(),
key_prefix: "auth:".to_string(),
};
self
}
pub fn rate_limiting(mut self, config: RateLimitConfig) -> Self {
self.rate_limiting = config;
self
}
pub fn security(mut self, config: SecurityConfig) -> Self {
self.security = config;
self
}
pub fn cors(mut self, config: CorsConfig) -> Self {
self.cors = config;
self
}
pub fn audit(mut self, config: AuditConfig) -> Self {
self.audit = config;
self
}
pub fn method_config(
mut self,
method_name: impl Into<String>,
config: impl Serialize,
) -> Result<Self> {
let value = serde_json::to_value(config)
.map_err(|e| AuthError::config(format!("Failed to serialize method config: {e}")))?;
self.method_configs.insert(method_name.into(), value);
Ok(self)
}
pub fn get_method_config<T>(&self, method_name: &str) -> Result<Option<T>>
where
T: for<'de> Deserialize<'de>,
{
if let Some(value) = self.method_configs.get(method_name) {
let config = serde_json::from_value(value.clone()).map_err(|e| {
AuthError::config(format!("Failed to deserialize method config: {e}"))
})?;
Ok(Some(config))
} else {
Ok(None)
}
}
pub fn validate(&self) -> Result<()> {
if self.token_lifetime.as_secs() == 0 {
return Err(AuthError::config("Token lifetime must be greater than 0"));
}
if self.refresh_token_lifetime.as_secs() == 0 {
return Err(AuthError::config(
"Refresh token lifetime must be greater than 0",
));
}
if self.refresh_token_lifetime <= self.token_lifetime {
return Err(AuthError::config(
"Refresh token lifetime must be greater than token lifetime",
));
}
self.validate_jwt_secret()?;
if self.security.min_password_length < 4 {
return Err(AuthError::config(
"Minimum password length must be at least 4 characters",
));
}
if self.is_production_environment() && !self.is_test_environment() {
self.validate_production_security()?;
}
if self.rate_limiting.enabled && self.rate_limiting.max_requests == 0 {
return Err(AuthError::config(
"Rate limit max requests must be greater than 0 when enabled",
));
}
self.validate_storage_config()?;
self.validate_method_configs()?;
Ok(())
}
fn validate_method_configs(&self) -> Result<()> {
for (method_name, raw_config) in &self.method_configs {
match method_name.as_str() {
#[cfg(feature = "saml")]
"saml" => {
let config: crate::methods::saml::SamlConfig =
serde_json::from_value(raw_config.clone()).map_err(|e| {
AuthError::config(format!(
"Failed to deserialize SAML method config: {e}"
))
})?;
if config.entity_id.trim().is_empty() {
return Err(AuthError::config("SAML entity_id cannot be empty"));
}
if config.acs_url.trim().is_empty() {
return Err(AuthError::config("SAML acs_url cannot be empty"));
}
if config.max_assertion_age == 0 {
return Err(AuthError::config(
"SAML max_assertion_age must be greater than 0",
));
}
}
#[cfg(not(feature = "saml"))]
"saml" => {
return Err(AuthError::config(
"SAML method config is present but the 'saml' feature is not enabled",
));
}
#[cfg(feature = "passkeys")]
"passkey" => {
let config: crate::methods::passkey::PasskeyConfig =
serde_json::from_value(raw_config.clone()).map_err(|e| {
AuthError::config(format!(
"Failed to deserialize passkey method config: {e}"
))
})?;
if config.rp_id.trim().is_empty() {
return Err(AuthError::config("Passkey RP ID cannot be empty"));
}
if config.origin.trim().is_empty() {
return Err(AuthError::config("Passkey origin cannot be empty"));
}
if config.timeout_ms == 0 {
return Err(AuthError::config("Passkey timeout must be greater than 0"));
}
match config.user_verification.as_str() {
"required" | "preferred" | "discouraged" => {}
_ => {
return Err(AuthError::config(
"Invalid passkey user_verification value",
));
}
}
url::Url::parse(&config.origin).map_err(|e| {
AuthError::config(format!("Invalid passkey origin URL: {e}"))
})?;
}
#[cfg(not(feature = "passkeys"))]
"passkey" => {
return Err(AuthError::config(
"Passkey method config is present but the 'passkeys' feature is not enabled",
));
}
"enhanced_device_flow" => {
let client_id = raw_config
.get("client_id")
.and_then(serde_json::Value::as_str)
.unwrap_or_default();
let auth_url = raw_config
.get("auth_url")
.and_then(serde_json::Value::as_str)
.unwrap_or_default();
let token_url = raw_config
.get("token_url")
.and_then(serde_json::Value::as_str)
.unwrap_or_default();
let device_auth_url = raw_config
.get("device_auth_url")
.and_then(serde_json::Value::as_str)
.unwrap_or_default();
if client_id.trim().is_empty()
|| auth_url.trim().is_empty()
|| token_url.trim().is_empty()
|| device_auth_url.trim().is_empty()
{
return Err(AuthError::config(
"Enhanced device flow config requires non-empty client_id, auth_url, token_url, and device_auth_url",
));
}
}
_ => {}
}
}
Ok(())
}
fn validate_jwt_secret(&self) -> Result<()> {
let env_secret = std::env::var("JWT_SECRET").ok();
let jwt_secret = self
.security
.secret_key
.as_ref()
.or(self.secret.as_ref())
.or(env_secret.as_ref());
if let Some(secret) = jwt_secret {
if !self.is_test_environment() && secret.len() < 32 {
return Err(AuthError::config(
"JWT secret must be at least 32 characters for security. \
Generate with: openssl rand -base64 32",
));
}
if !self.is_test_environment() {
let mut char_counts = std::collections::HashMap::new();
for c in secret.chars() {
*char_counts.entry(c).or_insert(0u32) += 1;
}
let len = secret.len() as f64;
let entropy: f64 = char_counts
.values()
.map(|&count| {
let p = count as f64 / len;
-p * p.log2()
})
.sum();
if entropy < 3.5 {
return Err(AuthError::config(
"JWT secret has insufficient entropy (too predictable). \
Use a cryptographically secure random string.",
));
}
}
if secret.len() < 44
&& secret
.chars()
.all(|c| c.is_alphanumeric() || c == '+' || c == '/' || c == '=')
{
tracing::warn!(
"JWT secret may be too short for optimal security. \
Consider using at least 44 characters (32 bytes base64-encoded)."
);
}
} else if self.is_production_environment() {
return Err(AuthError::config(
"JWT secret is required for production environments. \
Set JWT_SECRET environment variable or configure security.secret_key",
));
}
Ok(())
}
fn validate_production_security(&self) -> Result<()> {
if self.security.min_password_length < 8 {
return Err(AuthError::config(
"Production environments require minimum password length of 8 characters",
));
}
if !self.security.require_password_complexity {
tracing::warn!("Production deployment should enable password complexity requirements");
}
if !self.security.secure_cookies {
return Err(AuthError::config(
"Production environments must use secure cookies (HTTPS required)",
));
}
if !self.rate_limiting.enabled {
tracing::warn!("Production deployment should enable rate limiting for security");
}
if !self.audit.enabled {
return Err(AuthError::config(
"Production environments require audit logging for compliance",
));
}
Ok(())
}
fn validate_storage_config(&self) -> Result<()> {
match &self.storage {
StorageConfig::Memory => {
if self.is_production_environment() && !self.is_test_environment() {
return Err(AuthError::config(
"Memory storage is not suitable for production environments. \
Use PostgreSQL, Redis, MySQL, or SQLite storage.",
));
}
}
#[cfg(feature = "mysql-storage")]
StorageConfig::MySQL { .. } => {
tracing::warn!(
"MySQL storage has known RSA vulnerability (RUSTSEC-2023-0071). \
Consider using PostgreSQL for enhanced security."
);
}
_ => {} }
Ok(())
}
fn is_production_environment(&self) -> bool {
if self.force_production_mode {
return true;
}
if let Ok(env) = std::env::var("ENVIRONMENT")
&& (env.to_lowercase() == "production" || env.to_lowercase() == "prod")
{
return true;
}
if let Ok(env) = std::env::var("ENV")
&& (env.to_lowercase() == "production" || env.to_lowercase() == "prod")
{
return true;
}
if let Ok(env) = std::env::var("NODE_ENV")
&& env.to_lowercase() == "production"
{
return true;
}
if let Ok(env) = std::env::var("RUST_ENV")
&& env.to_lowercase() == "production"
{
return true;
}
if std::env::var("KUBERNETES_SERVICE_HOST").is_ok() {
return true;
}
if std::env::var("DOCKER_CONTAINER").is_ok() {
return true;
}
false
}
fn is_test_environment(&self) -> bool {
if self.force_production_mode {
return false;
}
cfg!(test)
|| std::thread::current()
.name()
.is_some_and(|name| name.contains("test"))
|| std::env::var("RUST_TEST").is_ok()
|| std::env::var("ENVIRONMENT").as_deref() == Ok("test")
|| std::env::var("ENV").as_deref() == Ok("test")
|| std::env::args().any(|arg| arg.contains("test"))
}
}
impl RateLimitConfig {
pub fn new(max_requests: u32, window: Duration) -> Self {
Self {
enabled: true,
max_requests,
window,
burst: max_requests / 10, }
}
pub fn disabled() -> Self {
Self {
enabled: false,
..Default::default()
}
}
pub fn per_second(max: u32) -> Self {
Self::new(max, Duration::from_secs(1))
}
pub fn per_minute(max: u32) -> Self {
Self::new(max, Duration::from_secs(60))
}
pub fn per_hour(max: u32) -> Self {
Self::new(max, Duration::from_secs(3600))
}
}
impl SecurityConfig {
pub fn secure() -> Self {
Self {
min_password_length: 12,
require_password_complexity: true,
password_hash_algorithm: PasswordHashAlgorithm::Argon2,
jwt_algorithm: JwtAlgorithm::RS256,
secret_key: None,
previous_secret_key: None,
secure_cookies: true,
cookie_same_site: CookieSameSite::Strict,
csrf_protection: true,
session_timeout: Duration::from_secs(3600 * 8), }
}
pub fn development() -> Self {
Self {
min_password_length: 6,
require_password_complexity: false,
password_hash_algorithm: PasswordHashAlgorithm::Bcrypt,
jwt_algorithm: JwtAlgorithm::HS256,
secret_key: None, previous_secret_key: None,
secure_cookies: false,
cookie_same_site: CookieSameSite::Lax,
csrf_protection: false,
session_timeout: Duration::from_secs(3600 * 24), }
}
}
#[derive(Debug)]
pub struct AuthConfigBuilder {
config: AuthConfig,
}
impl Default for AuthConfigBuilder {
fn default() -> Self {
Self::new()
}
}
impl AuthConfigBuilder {
pub fn new() -> Self {
Self {
config: AuthConfig::default(),
}
}
pub fn tokens(self) -> TokenConfigBuilder {
TokenConfigBuilder { builder: self }
}
pub fn security(self) -> SecurityConfigBuilder {
SecurityConfigBuilder { builder: self }
}
pub fn storage(self) -> StorageConfigBuilder {
StorageConfigBuilder { builder: self }
}
pub fn features(self) -> FeatureConfigBuilder {
FeatureConfigBuilder { builder: self }
}
pub fn rate_limiting(self) -> RateLimitConfigBuilder {
RateLimitConfigBuilder { builder: self }
}
pub fn cors(self) -> CorsConfigBuilder {
CorsConfigBuilder { builder: self }
}
pub fn audit(self) -> AuditConfigBuilder {
AuditConfigBuilder { builder: self }
}
pub fn build(self) -> Result<AuthConfig> {
let config = self.config;
config.validate()?;
Ok(config)
}
}
#[derive(Debug)]
pub struct TokenConfigBuilder {
builder: AuthConfigBuilder,
}
impl TokenConfigBuilder {
pub fn lifetime(mut self, duration: Duration) -> Self {
self.builder.config.token_lifetime = duration;
self
}
pub fn refresh_lifetime(mut self, duration: Duration) -> Self {
self.builder.config.refresh_token_lifetime = duration;
self
}
pub fn issuer(mut self, issuer: impl Into<String>) -> Self {
self.builder.config.issuer = issuer.into();
self
}
pub fn audience(mut self, audience: impl Into<String>) -> Self {
self.builder.config.audience = audience.into();
self
}
pub fn secret(mut self, secret: impl Into<String>) -> Self {
self.builder.config.secret = Some(secret.into());
self
}
pub fn done(self) -> AuthConfigBuilder {
self.builder
}
}
#[derive(Debug)]
pub struct SecurityConfigBuilder {
builder: AuthConfigBuilder,
}
impl SecurityConfigBuilder {
pub fn min_password_length(mut self, length: usize) -> Self {
self.builder.config.security.min_password_length = length;
self
}
pub fn require_complexity(mut self, required: bool) -> Self {
self.builder.config.security.require_password_complexity = required;
self
}
pub fn password_algorithm(mut self, algorithm: PasswordHashAlgorithm) -> Self {
self.builder.config.security.password_hash_algorithm = algorithm;
self
}
pub fn jwt_algorithm(mut self, algorithm: JwtAlgorithm) -> Self {
self.builder.config.security.jwt_algorithm = algorithm;
self
}
pub fn secure_cookies(mut self, enabled: bool) -> Self {
self.builder.config.security.secure_cookies = enabled;
self
}
pub fn cookie_same_site(mut self, policy: CookieSameSite) -> Self {
self.builder.config.security.cookie_same_site = policy;
self
}
pub fn csrf_protection(mut self, enabled: bool) -> Self {
self.builder.config.security.csrf_protection = enabled;
self
}
pub fn session_timeout(mut self, timeout: Duration) -> Self {
self.builder.config.security.session_timeout = timeout;
self
}
pub fn done(self) -> AuthConfigBuilder {
self.builder
}
}
#[derive(Debug)]
pub struct StorageConfigBuilder {
builder: AuthConfigBuilder,
}
impl StorageConfigBuilder {
pub fn memory(mut self) -> Self {
self.builder.config.storage = StorageConfig::Memory;
self
}
#[cfg(feature = "redis-storage")]
pub fn redis(mut self, url: impl Into<String>) -> Self {
self.builder.config.storage = StorageConfig::Redis {
url: url.into(),
key_prefix: "auth:".to_string(),
};
self
}
#[cfg(feature = "postgres-storage")]
pub fn postgres(mut self, connection_string: impl Into<String>) -> Self {
self.builder.config.storage = StorageConfig::Postgres {
connection_string: connection_string.into(),
table_prefix: "auth_".to_string(),
};
self
}
#[cfg(feature = "mysql-storage")]
pub fn mysql(mut self, connection_string: impl Into<String>) -> Self {
self.builder.config.storage = StorageConfig::MySQL {
connection_string: connection_string.into(),
table_prefix: "auth_".to_string(),
};
self
}
#[cfg(feature = "sqlite-storage")]
pub fn sqlite(mut self, connection_string: impl Into<String>) -> Self {
self.builder.config.storage = StorageConfig::Sqlite {
connection_string: connection_string.into(),
};
self
}
pub fn done(self) -> AuthConfigBuilder {
self.builder
}
}
#[derive(Debug)]
pub struct FeatureConfigBuilder {
builder: AuthConfigBuilder,
}
impl FeatureConfigBuilder {
pub fn enable_multi_factor(mut self, enabled: bool) -> Self {
self.builder.config.enable_multi_factor = enabled;
self
}
pub fn enable_rbac(mut self, enabled: bool) -> Self {
self.builder.config.enable_rbac = enabled;
self
}
pub fn enable_caching(mut self, enabled: bool) -> Self {
self.builder.config.enable_caching = enabled;
self
}
pub fn enable_middleware(mut self, enabled: bool) -> Self {
self.builder.config.enable_middleware = enabled;
self
}
pub fn max_failed_attempts(mut self, max: u32) -> Self {
self.builder.config.max_failed_attempts = max;
self
}
pub fn done(self) -> AuthConfigBuilder {
self.builder
}
}
#[derive(Debug)]
pub struct RateLimitConfigBuilder {
builder: AuthConfigBuilder,
}
impl RateLimitConfigBuilder {
pub fn enabled(mut self, max_requests: u32, window: Duration) -> Self {
self.builder.config.rate_limiting = RateLimitConfig::new(max_requests, window);
self
}
pub fn disabled(mut self) -> Self {
self.builder.config.rate_limiting = RateLimitConfig::disabled();
self
}
pub fn max_requests(mut self, max: u32) -> Self {
self.builder.config.rate_limiting.max_requests = max;
self
}
pub fn window(mut self, window: Duration) -> Self {
self.builder.config.rate_limiting.window = window;
self
}
pub fn burst(mut self, burst: u32) -> Self {
self.builder.config.rate_limiting.burst = burst;
self
}
pub fn done(self) -> AuthConfigBuilder {
self.builder
}
}
#[derive(Debug)]
pub struct CorsConfigBuilder {
builder: AuthConfigBuilder,
}
impl CorsConfigBuilder {
pub fn allow_origins<I, S>(mut self, origins: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.builder.config.cors = CorsConfig::for_origins(origins);
self
}
pub fn disabled(mut self) -> Self {
self.builder.config.cors.enabled = false;
self
}
pub fn allow_methods<I, S>(mut self, methods: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.builder.config.cors.allowed_methods = methods.into_iter().map(Into::into).collect();
self
}
pub fn allow_headers<I, S>(mut self, headers: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.builder.config.cors.allowed_headers = headers.into_iter().map(Into::into).collect();
self
}
pub fn max_age(mut self, seconds: u32) -> Self {
self.builder.config.cors.max_age_secs = seconds;
self
}
pub fn done(self) -> AuthConfigBuilder {
self.builder
}
}
#[derive(Debug)]
pub struct AuditConfigBuilder {
builder: AuthConfigBuilder,
}
impl AuditConfigBuilder {
pub fn enabled(mut self) -> Self {
self.builder.config.audit.enabled = true;
self
}
pub fn disabled(mut self) -> Self {
self.builder.config.audit.enabled = false;
self
}
pub fn log_success(mut self, enabled: bool) -> Self {
self.builder.config.audit.log_success = enabled;
self
}
pub fn log_failures(mut self, enabled: bool) -> Self {
self.builder.config.audit.log_failures = enabled;
self
}
pub fn log_permissions(mut self, enabled: bool) -> Self {
self.builder.config.audit.log_permissions = enabled;
self
}
pub fn log_tokens(mut self, enabled: bool) -> Self {
self.builder.config.audit.log_tokens = enabled;
self
}
pub fn done(self) -> AuthConfigBuilder {
self.builder
}
}