use std::collections::HashMap;
#[derive(Clone, Debug)]
pub struct AuthConfig {
users: HashMap<String, String>,
allow_default: bool,
disabled: bool,
}
impl Default for AuthConfig {
fn default() -> Self {
Self {
users: HashMap::new(),
allow_default: true,
disabled: false,
}
}
}
impl AuthConfig {
pub fn from_env() -> Self {
if auth_disabled_from_env() {
return Self::disabled();
}
match std::env::var("ELEKTROMAIL_USERS") {
Ok(value) => Self::from_env_value(&value),
Err(_) => Self::default(),
}
}
pub(crate) fn from_env_value(value: &str) -> Self {
let users = parse_users(value);
let allow_default = users.is_empty();
Self {
users,
allow_default,
disabled: false,
}
}
pub fn from_users(value: &str) -> Self {
Self::from_env_value(value)
}
pub fn disabled() -> Self {
Self {
users: HashMap::new(),
allow_default: true,
disabled: true,
}
}
pub fn authenticate(&self, user: &str, pass: &str) -> bool {
if self.disabled {
return true;
}
if !self.users.is_empty() {
return self.users.get(user).is_some_and(|stored| stored == pass);
}
self.allow_default && user == "user" && pass == "pass"
}
pub fn user_count(&self) -> usize {
self.users.len()
}
}
fn parse_users(value: &str) -> HashMap<String, String> {
let mut users = HashMap::new();
for entry in value.split(',') {
let entry = entry.trim();
if entry.is_empty() {
continue;
}
let mut parts = entry.split(':').map(str::trim).filter(|p| !p.is_empty());
let user = parts.next();
let second = parts.next();
let third = parts.next();
let (user, pass) = match (user, second, third) {
(Some(user), Some(pass), None) => (user, pass),
(Some(user), Some(_email), Some(pass)) => (user, pass),
_ => continue,
};
if !user.is_empty() && !pass.is_empty() {
users.insert(user.to_string(), pass.to_string());
}
}
users
}
fn auth_disabled_from_env() -> bool {
match std::env::var("ELEKTROMAIL_AUTH_DISABLED") {
Ok(value) => matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "YES"),
Err(_) => false,
}
}
#[cfg(test)]
mod tests {
use super::AuthConfig;
#[test]
fn env_value_parses_single_user() {
let config = AuthConfig::from_env_value("demo:demo");
assert!(config.authenticate("demo", "demo"));
assert!(!config.authenticate("user", "pass"));
}
#[test]
fn env_value_parses_multiple_users() {
let config = AuthConfig::from_env_value("demo:demopass,test:testpass");
assert!(config.authenticate("demo", "demopass"));
assert!(config.authenticate("test", "testpass"));
assert!(!config.authenticate("demo", "wrong"));
}
#[test]
fn env_value_parses_three_part_entries() {
let config = AuthConfig::from_env_value("demo:demo@localhost:demopass");
assert!(config.authenticate("demo", "demopass"));
}
#[test]
fn empty_env_value_falls_back_to_default() {
let config = AuthConfig::from_env_value("");
assert!(config.authenticate("user", "pass"));
}
}