summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/api/auth.rs33
-rw-r--r--src/main.rs3
2 files changed, 24 insertions, 12 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)),
- }
+ })
}
}
diff --git a/src/main.rs b/src/main.rs
index 99ee1b9..dee0a32 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -60,6 +60,8 @@ async fn main() -> Result<()> {
// First thing: until a handler is installed, SIGINT and SIGTERM kill the process on
// the spot, and startup (spawning Mycelium, bootstrap) can take minutes.
let stop = watch_for_shutdown().context("Installing signal handlers")?;
+ // A broken htpasswd will abort startup.
+ let auth_state = AuthState::new(&args.htpasswd)?;
let mycelium_setup = {
let spawn_config = MyceliumSpawnConfig {
@@ -223,7 +225,6 @@ async fn main() -> Result<()> {
);
swarm::state_file::update(&state, true).await;
- let auth_state = AuthState::new(&args.htpasswd);
let app = api::router(state.clone(), auth_state, args.debug_api);
let listener = tokio::net::TcpListener::bind(args.bind_addr)