summaryrefslogtreecommitdiffstats
path: root/src/api/auth.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-10-26 20:16:41 +0100
committertv <tv@krebsco.de>2026-10-26 20:16:41 +0100
commitf3eb7ee93ee2544cb5afa6a7ffcf88d580214fb9 (patch)
tree541862582923c56a95e8f13d7a04beb8b8d97282 /src/api/auth.rs
parenta3b7b4dd5bbee6bedaf0880732e26464cc1c8dec (diff)
detect broken htpasswd files during startup
Diffstat (limited to 'src/api/auth.rs')
-rw-r--r--src/api/auth.rs33
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)),
- }
+ })
}
}