From d48aa13cad73030095fd2677ec0b4cea95912eba Mon Sep 17 00:00:00 2001 From: tv Date: Sun, 8 Nov 2026 15:35:25 +0000 Subject: auth: no password prompt over the dashboard's own requests Opening a dashboard through its login page showed a 401 on /auth/login and the browser's password prompt. The page asked for its token by the browser's password first (POST /auth/login), with none to send, and the 401 carried a Basic challenge, which a browser answers with its prompt. The page now asks by its session first (POST /auth/session), then by the browser's password or the proxy's. And a 401 to a page's own requests (Sec-Fetch-Dest: empty: fetch, EventSource) carries no challenge: a page has its login of its own. Navigations and clients without the header (curl) are still challenged. --- src/api/auth.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'src/api/auth.rs') diff --git a/src/api/auth.rs b/src/api/auth.rs index f28aa22..acf9d46 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -316,15 +316,20 @@ pub(crate) async fn basic_auth( ) .into_response(); } - // A challenge only where basic authentication can answer it. + // A challenge only where basic authentication can answer it, and not to 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`). + let from_a_page = headers + .get("sec-fetch-dest") + .is_some_and(|dest| dest == "empty"); return match state.users { - Some(_) => ( + Some(_) if !from_a_page => ( StatusCode::UNAUTHORIZED, [(WWW_AUTHENTICATE, r#"Basic realm="Restricted""#)], "Unauthorized", ) .into_response(), - None => (StatusCode::UNAUTHORIZED, "Unauthorized").into_response(), + _ => (StatusCode::UNAUTHORIZED, "Unauthorized").into_response(), }; }; -- cgit v1.3.1