use std::{collections::BTreeSet, time::Duration};
use serde::{Deserialize, Serialize};
use super::{password::validate_password_hash, token::expiry_after};
use crate::server::error::{Result, ServerError};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields, default)]
pub struct AuthConfig {
pub mode: AuthMode,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum AuthMode {
#[default]
Disabled,
BasicBearer {
#[serde(default)]
users: Vec<StaticUserConfig>,
token_ttl_secs: u64,
},
}
impl AuthMode {
pub fn token_ttl(&self) -> Option<Duration> {
match self {
AuthMode::Disabled => None,
AuthMode::BasicBearer { token_ttl_secs, .. } => Some(Duration::from_secs(*token_ttl_secs)),
}
}
pub fn validate(&self) -> Result<()> {
match self {
AuthMode::Disabled => Ok(()),
AuthMode::BasicBearer { users, token_ttl_secs } => {
if users.is_empty() {
return Err(ServerError::configuration(
"auth.mode.basic_bearer requires at least one entry in `users`",
));
}
if *token_ttl_secs == 0 {
return Err(ServerError::configuration(
"auth.mode.token_ttl_secs must be greater than zero; non-expiring bearer tokens are not supported",
));
}
if expiry_after(Duration::from_secs(*token_ttl_secs)).is_none() {
return Err(ServerError::configuration(
"auth.mode.token_ttl_secs is too large to compute a token expiry",
));
}
let mut seen = BTreeSet::new();
for user in users {
if user.username.is_empty() {
return Err(ServerError::configuration("auth user `username` must be non-empty"));
}
if !seen.insert(user.username.as_str()) {
return Err(ServerError::configuration(format!(
"auth users contain duplicate username `{}`",
user.username
)));
}
if let Err(error) = validate_password_hash(&user.password_hash) {
return Err(ServerError::configuration(format!(
"auth user `{}` password_hash must be a valid Argon2 PHC string: {error}",
user.username,
)));
}
}
Ok(())
},
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct StaticUserConfig {
pub username: String,
pub password_hash: String,
}