aboutsummaryrefslogtreecommitdiffstats
path: root/src/api/auth.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-09 11:50:37 +0000
committertv <tv@krebsco.de>2026-11-09 12:51:20 +0100
commit00b80d182363e03d447be0a6c1812ec46f8faee1 (patch)
tree41f43ca17242afbf763e0f87ee5951e0c3f680d0 /src/api/auth.rs
parent8a7df0caa51fc1b01e2a744c9e48dd3a1af39296 (diff)
dashboard: tokens renewed after sleep too, never basic authentication
The page renewed its token half way through its life by a timer. A timer fires late where the computer slept or the tab was in the background, by when the token (and the session cookie, which lives as long) may have ended: the page then fell back to `POST /auth/login`, which takes a password the browser keeps (basic authentication), and without one retried every 30 s, logged out without saying so. Now whether the token is half way through its life is looked at again when the page shows and every 30 s (`refreshIfDue`, one renewal at a time), so it is renewed while it still can be, until the login ends (`--session-lifetime`). Where the login ended, or there is none (400, 401, 403), the page goes to the login page; a node away is tried again in 30 s. And the page no longer gets in by a password the browser keeps: only by its session or through a proxy naming the user (`throughProxy`, `login: "header"`). The node never challenges a browser for a password either (`WWW-Authenticate`, the browser's prompt): not only on the dashboard's paths and for a page's own requests, but for any request with `Sec-Fetch-*` or a `Mozilla/…` user agent. curl sends the password unasked with `-u`, as before.
Diffstat (limited to 'src/api/auth.rs')
-rw-r--r--src/api/auth.rs18
1 files changed, 11 insertions, 7 deletions
diff --git a/src/api/auth.rs b/src/api/auth.rs
index f4806ca..abb8c1a 100644
--- a/src/api/auth.rs
+++ b/src/api/auth.rs
@@ -324,15 +324,19 @@ pub(crate) async fn basic_auth(
)
.into_response();
}
- // A challenge only where basic authentication can answer it, for the API (curl),
- // never for what is meant for browsers: the dashboard, nor a page's own requests
- // (`Sec-Fetch-Dest: empty`: fetch, EventSource). The browser would ask for a
- // password over the page, which has its own login (`/login`), taking the
- // passwords of the same file.
+ // A challenge only where basic authentication can answer it, for the API (curl,
+ // which sends the password unasked with `-u` anyway), never to a browser: it would
+ // ask for a password over the page, which has its own login (`/login`), taking
+ // the passwords of the same file. A browser: the dashboard's paths, any request
+ // with `Sec-Fetch-*` (every browser for years: pages, downloads, fetch,
+ // EventSource), or one calling itself `Mozilla/…`, as every browser does.
let for_browsers = request.uri().path().starts_with("/admin/dashboard")
+ || headers.contains_key("sec-fetch-dest")
+ || headers.contains_key("sec-fetch-mode")
|| headers
- .get("sec-fetch-dest")
- .is_some_and(|dest| dest == "empty");
+ .get(axum::http::header::USER_AGENT)
+ .and_then(|agent| agent.to_str().ok())
+ .is_some_and(|agent| agent.starts_with("Mozilla/"));
return match state.users {
Some(_) if !for_browsers => (
StatusCode::UNAUTHORIZED,