OIDC browser login is dead: root 401s with no login path, /_auth/login 501s, and incomplete OIDC config boots silently #344
Labels
No labels
actions
bug
cli
duplicate
enhancement
fork
forum
git storage
help wanted
insights
invalid
issues
moderation
oidc
ownership transfer
packages
pr/merge protection rules
projects
pull requests
question
releases
sponsorships
tags
webhooks
wiki
wontfix
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
crueber/walhub#344
Loading…
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?
What's wrong
With OIDC configured on hub.packden.us, browser login is completely dead and there is no way in:
/returns 401 plain textauthentication required(withWWW-Authenticate: Bearer) for a normal browser GET — no redirect to the authenticator, no login page, no hint about what to do.GET /_auth/loginanswers 501browser login is not enabled.curl -i https://hub.packden.us/with a browserAccept: text/html→ 401;curl -i https://hub.packden.us/_auth/login→ 501.Root cause (code evidence)
authFailure(internal/server/middleware.go:529-546) sends browser-ish GETs without anAuthorizationheader to/_auth/login?next=…(307) only whens.authSvc.BrowserLoginEnabled()(middleware.go:537-541) — otherwise it falls through to the bare 401 at :543-544.BrowserLoginEnabled()requires all four of:mode == "oidc"and non-emptysession_secretand non-emptyoauth_client_idand non-emptyoauth_client_secret(internal/server/auth.go:306-310). The live/ _auth/login501 ("browser login is not enabled",auth_oidc.go) proves one or more of those is empty on the running instance — the same state makes every browser GET a dead-end 401, because anonymous read is also off in OIDC mode.internal/config/validate.go:111-115only checks thatoauth_client_id/oauth_client_secretare paired, and that a non-emptysession_secretis ≥ 32 bytes. Nothing requires the trio whenauth.mode == "oidc", so a partially-configured OIDC instance boots happily into an unusable state — no startup error, no health signal, no setup-page warning. That is the systemic defect: the misconfiguration is silent.{"bearer": true, "setup": …, "browser": …, "authenticate": …}(live,GET /api/v1) — nologin/browser_loginmember — and no page renders a "Log in with OIDC" button. Even when enabled, an unauthenticated browser that somehow reaches the SPA has no rendered affordance (the spec's answer is the 307 into the provider, which never fires when disabled).docs/go/06_server_http.md:62specifies the 307-to-/_auth/loginbehavior for browser-ish GETs. The code implements it, but the spec's precondition (browser login enabled) is unverifiable at boot — the doc should also state what a browser sees when the precondition is unmet (today: an unexplained 401).Note on the reported trigger
"Restarted with credentials and now everything 401s" is consistent with #3: a plausible key-name or partial-set mistake (
oauth_client_id/oauth_client_secret/session_secretunderserver.auth, or theirWALHUB__SERVER__AUTH__*env spellings) leaves the trio incomplete while the process starts clean. The audit should confirm the exact running config (or at least enumerate the three keys and the env spellings in the issue thread) and then decide whether the primary fix is validation, a login page, or both.What's needed
auth.mode == "oidc"and the browser-login trio is incomplete, config validation must refuse to start (or boot into a clearly-signposted degraded state that still serves a login/diagnostic page) — never the silent current state. Name the missing key(s) in the error./_auth/login), plus a clear explanation when browser login is disabled. The/_auth/login501 body ("browser login is not enabled") is a developer string; it must not be what an end user at/effectively experiences./services/setup.json) so the SPA can render the login button only when it works, and so/setupcan warn when OIDC is selected without the trio.mode=oidcwith each of the three keys missing; a middleware test asserting a browser-ish GET withAccept: text/htmlredirects to/_auth/login?next=…when enabled and returns the diagnostic page (not a bare 401) when not.Acceptance criteria
/on an OIDC-configured instance either 307s to the provider or renders a page with a working "Log in with OIDC" button; never a bareauthentication requiredwith no path forward.auth.mode = "oidc"with any ofsession_secret/oauth_client_id/oauth_client_secretmissing fails config validation with an error naming the missing key(s) — no silent boot.GET /api/v1auth block) and/or setup JSON report browser-login availability;/setupwarns when OIDC is selected but the trio is incomplete./_auth/loginwith browser login enabled starts the provider flow and returnsnextcorrectly after callback; the 501 path is unreachable in a validly-configured instance.Ties to #345 (public/private visibility)
This issue and #345 (public repos viewable without an account) must land coherently, because public becomes the default repo visibility there:
server.auth.anonymous_read = false, which is exactly the gate currently 401-ing this instance's root URL./on a healthy instance should render the public app (explore, public repos) with a "Log in with OIDC" affordance from this issue — the 401-with-no-path-forward disappears for repos the visitor can read, and this issue's login flow covers everything else.Env.gate,smart.go,lfs.go, the SPA shell'sgated()): whichever lands first must not assume the other's semantics. Sequence note for the implementer: #345 changes what anonymous users can read; this issue changes how an unauthenticated browser gets an identity. The broken state being fixed here (bare 401, no login path, silent misconfig boot) is independent of visibility and must be fixed regardless.mode=oidcwithout the session/client trio) stays required under #345's semantics — anonymous browsing of public repos does not remove the need for a working login flow.Fix landed as PR #352 (branch fix/issue-344): #352 — validation refuses mode=oidc without the trio (naming keys), browsers get 307-or-login-page instead of bare 401/501-string, browser_login/login_url advertised in discovery + setup.json, /setup warns per-key. Bearer/token paths untouched; #345 coherence noted in the PR. Not merging per instructions — review requested.
Review of PR #352 (fix/issue-344) against #344's six acceptance criteria + #345 coherence. Verified in scratch worktrees (main worktree untouched, still clean on main). No browser used — tests + reasoning only, as instructed; no docker, no system packages, live instance untouched.
VERDICT: ready to merge (one comment-only fix pushed as
8f88de6; details below).Trio validation refuses, names keys, non-oidc exempt — PASS.
internal/config/validate.gocheckOIDCearly-returns nil for non-oidc modes, and the new trio block names each missing key (server.auth.session_secret/oauth_client_id/oauth_client_secret). Pinned byTestValidateOIDCTrioMissing(each-missing x3, all-missing, non-oidc exempt).Refusal composes with setup-only mode correctly:
LoadrunsValidate, errors ->resolveConfigstateInvalid -> serve-only-/setup+health with 503 elsewhere (cmd/walhub/serve.go:43-58,config.go:32-58). So "refuse to start" (PR body wording) is mechanistically "invalid file -> setup-only" per law 9+10 — the standard fail-closed path, same as the pre-existing oidc-allowlist refusal. Doc amendments say "refuses", matching law 9's existing language. No code issue; suggest softening "refuse-to-start" to "refuse-to-serve (setup-only)" in the PR body if edited further.Browser entry: 307 enabled / 401+page disabled — PASS.
authFailure(internal/server/middleware.go): GET + browserLooks + no Authorization + enabled -> 307/_auth/login?next=; same minus enabled -> 401 withWWW-Authenticate: Bearer realm="walgit"retained andtext/htmllogin page. Status preserved, challenge retained. Button href/_auth/login?next=<QueryEscape(RequestURI)>correct./_auth/loginrenders the same page (501) for browsers, plain string otherwise (auth_oidc.go:78-90). Hostile next (//evil, scheme URL) sanitizes to/before state-signing, tested.Git-client safety (CRITICAL) holds:
browserLooks= Accept contains text/html OR Sec-Fetch-Dest: document OR UA contains Mozilla. Git sendsUser-Agent: git/*+Accept: */*— never matches; smart-HTTP/LFS routes authenticate inside handlers, untouched. Plain/JSON-Accept clients keeptext/plainbodies (tested both directions). No HTML can leak to credential helpers.Nits (non-blocking): (a) FIXED by me (pushed
8f88de6):loginUnavailableHTMLcomment claimed sanitizeNext confined next at all call sites — true only for the/_auth/loginsite; the middleware site passes raw RequestURI and safety comes from QueryEscape. Comment now states the actual mechanism. Re-tested -race, gofmt clean. (b) Suggestion only: the page text is OIDC-specific but also serves token/none-mode instances (anonymous_read=false) where OIDC was never intended — "Log in with OIDC" button is dead there. Acceptable per criterion 2 (generic disabled explanation present); a mode-aware variant could be a follow-up.Bearer/token paths unaffected — PASS.
TestStaticTokensWorkInOIDCModepins static-token auth in oidc mode.Discovery + setup.json + /setup warnings — PASS.
browser_login+login_urlinGET /api/v1(always-present keys, URL empty when disabled) and/services/setup.json(browser_loginbool,login_urlabsolute only when enabled);web/src/lib/setup.jsfails each missing trio key on its own row pre-save. Minor asymmetry (discovery""vs setup.json absent key) is tested on both sides — harmless.#345 coherence — PASS. Diff touches none of Env.gate/smart.go/lfs.go/shell gating; only the login path in authFailure. No visibility semantics assumed; anonymous_read=false still gates as before.
Fixtures — intended-rule updates, not weakenings.
validate_testbase +setup_merge_testfile fixture +setup-formoidc bases add the now-required trio so the previously-valid cases stay valid under the new rule. No budget assertion touched.Verification results (scratch worktree @
8f88de6): config 95.7% / api 95.4% / server 96.6% (all >= 95%); -race pass on config+api full packages and all six new server tests + trio/discovery tests; gofmt/vet clean; node --test on setup-oidc-trio + setup-form 53/53; go build clean. Fullinternal/serverpackage shows failures ONLY of the form "ui shell missing — run make build" (unbuilt web/dist in scratch worktrees); confirmed identical failures on a pristine main worktree, i.e. pre-existing environmental, unrelated to this PR. (One transientTestVerifyTokenWireNegatives/tampered_mac-adjacent line in an earlier run did not recur and the test passes in isolation and in the re-run — flake, not PR-related.)Docs — accurate. 06_server_http.md §2.2#8 row now states the disabled-browser 401+page (closes the spec gap the issue flagged) + §14 entry with the refuse-vs-degraded rationale and #345 layering note; 07_api.md shape; 11_config_cli.md rule 2 + §14 entry. Code matches all three.
MERGE RECOMMENDATION: ready to merge.
Fixed by PR #352 (review clean + one comment-accuracy fix by reviewer; trio refusal, browser entry, git-client safety, #345 coherence verified), merged. Closing.