diff options
| author | tv <tv@krebsco.de> | 2026-10-26 20:16:41 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-10-26 20:16:41 +0100 |
| commit | f3eb7ee93ee2544cb5afa6a7ffcf88d580214fb9 (patch) | |
| tree | 541862582923c56a95e8f13d7a04beb8b8d97282 /src/api/auth.rs | |
| parent | a3b7b4dd5bbee6bedaf0880732e26464cc1c8dec (diff) | |
detect broken htpasswd files during startup
Diffstat (limited to 'src/api/auth.rs')
| -rw-r--r-- | src/api/auth.rs | 33 |
1 files changed, 22 insertions, 11 deletions
diff --git a/src/api/auth.rs b/src/api/auth.rs index de4220c..c07db74 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -1,3 +1,4 @@ +use anyhow::Context; use argon2::{Argon2, password_hash::PasswordVerifier}; use axum::extract::State; use axum::{ @@ -21,18 +22,28 @@ use std::{ use tokio::sync::Semaphore; use tokio::time::sleep; -fn load_users(path: &str) -> std::io::Result<HashMap<String, String>> { - let file = File::open(path)?; +/// The users of an htpasswd file (`user:hash` lines; empty lines and `#` comments are +/// skipped). A line that is neither, or a file without users, is an error: every request +/// would be refused, which is better told at startup. +fn load_users(path: &str) -> anyhow::Result<HashMap<String, String>> { + let file = File::open(path).with_context(|| format!("Opening the htpasswd file {path}"))?; let reader = BufReader::new(file); let mut users = HashMap::new(); - for line in reader.lines() { - let line = line?; - - if let Some((user, hash)) = line.split_once(':') { - users.insert(user.to_owned(), hash.to_owned()); + for (number, line) in reader.lines().enumerate() { + let line = line.with_context(|| format!("Reading the htpasswd file {path}"))?; + let line = line.trim(); + if line.is_empty() || line.starts_with('#') { + continue; } + let Some((user, hash)) = line.split_once(':') else { + anyhow::bail!("{path}, line {}: not `user:hash`", number + 1); + }; + users.insert(user.to_owned(), hash.to_owned()); + } + if users.is_empty() { + anyhow::bail!("The htpasswd file {path} has no users"); } Ok(users) @@ -49,15 +60,15 @@ pub struct AuthState { } impl AuthState { - pub fn new(path: &str) -> Self { - let users = load_users(path).unwrap_or_default(); + pub fn new(path: &str) -> anyhow::Result<Self> { + let users = load_users(path)?; - Self { + Ok(Self { idle_timeout: Duration::from_secs(300), cache: Arc::new(DashMap::new()), users: Arc::new(users), verify_slots: Arc::new(Semaphore::new(2)), - } + }) } } |
