Browser fetches must never receive a Basic challenge (global popup hazard) + wizard hands over to /setup #168
Labels
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: Cordy/Cairn#168
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Follow-up to #166, raised by Nikola after the v0.5.6 retest: is the native sign-in popup hazard global? Yes.
Diagnosis.
PasswordLogin.Challengedetects browsers byAccept: text/html— which only navigations send. Anyfetch()/XHR from the app (bell poll, file list, settings) that arrives unauthenticated falls through to the inner provider'sWWW-Authenticate: Basicchallenge, and browsers respond to that header on fetch responses with their NATIVE sign-in dialog over the open page. This is not local/LDAP-only: the dogfood's own log shows unauthenticated/api/v1/notifications401s with 24-byte bodies ("authentication required"— a Basic challenger, reached via the Multi provider's app-password arm in OIDC mode). An expired session with the app open pops the dialog on every instance.Fix, at the definition rather than per call site (the #153 sentinel lesson applied to auth): in
auth.Middleware, before delegating to any provider'sChallenge— if the request carriesSec-Fetch-Modeother thannavigate, answer plain401 {"error":"unauthenticated"}with no challenge header. Every modern browser sendsSec-Fetch-*on every request; no WebDAV client (Finder, rclone, Cyberduck) or API tool does. Consequences: browser fetches can never trigger the popup in ANY auth mode, navigations still get each provider's challenge (branded/loginredirect, IdP redirect), WebDAV keeps plain Basic untouched. Pre-2023 browsers withoutSec-Fetch-Modedegrade to today's behavior.Also: the wizard's restart handover goes to
/setupinstead of/login— on a first run the operator's next step is creating the admin account, and/setupself-redirects to/loginwhen first-run is not pending (or the auth middleware's navigation redirect does), so the non-first-run case stays correct with no extra probe (probing a status endpoint from a pre-auth page is exactly what #166 removed).Shipped in v0.5.7 (PR #169), live on both instances.
The chokepoint fix:
auth.Middlewarenow answers any request carrying a browser'sSec-Fetch-Mode(other thannavigate) with plain401 {"error":"unauthenticated"}before any providerChallengeruns — so no browserfetch()can ever receiveWWW-Authenticate: Basicin ANY auth mode, and the native sign-in dialog is structurally unreachable from the app. Navigations still get each provider's challenge (branded/loginredirect for local/LDAP, IdP redirect for OIDC), and WebDAV clients — which send noSec-Fetch-*— keep the Basic challenge Finder and rclone depend on. Three tests pin all three routes, including asserting the challenge header does NOT leak to fetches.Enforced at the one middleware rather than in each provider's
Challengefor the same reason v0.5.4 fixedErrNotFoundat its definition: a rule enforced at the chokepoint covers every provider, present and future — including theMultiapp-password arm that made this reachable even in OIDC mode.Wizard handover: now lands on
/setup— on a first run, creating the admin is the operator's actual next step; when first-run is not pending,/setupforwards to/loginon its own, and the non-pending navigation path is popup-free by the same middleware rule.Known degradation, accepted: pre-2023 browsers without
Sec-Fetch-Modekeep the old behavior.