fix(users): OIDCEnabled missing from failed-login render struct (bookshelf-xhqy) #608

Merged
zombor merged 1 commit from bd-bookshelf-xhqy into main 2026-06-18 14:16:23 +00:00
Owner

Summary

Regression from bookshelf-4op.6 (PR #605, OIDC): POST /login with wrong credentials re-renders login.html via an anonymous struct that was missing OIDCEnabled. Since login.html:46 references {{if .OIDCEnabled}}, template execution fails with can't evaluate field OIDCEnabled → 500 instead of the correct 401 + error page.

Fix: add OIDCEnabled bool and OIDCEnabled: d.OIDCEnabled to the failed-login error-render struct in loginHandler, matching the loginPageHandler (GET) pattern exactly.

Tests:

  • Updated testAuthRenderer login template stub to reference .OIDCEnabled, so any future regression is caught at unit-test time
  • Added "with invalid credentials"It("renders oidc=false ...") to pin the default case
  • Added new "with invalid credentials and OIDCEnabled=true" context asserting 401 + oidc=true in re-rendered body

Test plan

  • make build green
  • make lint clean (users package)
  • make coverage 100% gate passes
  • All 511 users package specs pass

Closes bead bookshelf-xhqy on merge.

## Summary Regression from bookshelf-4op.6 (PR #605, OIDC): `POST /login` with wrong credentials re-renders `login.html` via an anonymous struct that was missing `OIDCEnabled`. Since `login.html:46` references `{{if .OIDCEnabled}}`, template execution fails with `can't evaluate field OIDCEnabled` → 500 instead of the correct 401 + error page. **Fix:** add `OIDCEnabled bool` and `OIDCEnabled: d.OIDCEnabled` to the failed-login error-render struct in `loginHandler`, matching the `loginPageHandler` (GET) pattern exactly. **Tests:** - Updated `testAuthRenderer` login template stub to reference `.OIDCEnabled`, so any future regression is caught at unit-test time - Added `"with invalid credentials"` → `It("renders oidc=false ...")` to pin the default case - Added new `"with invalid credentials and OIDCEnabled=true"` context asserting 401 + `oidc=true` in re-rendered body ## Test plan - [x] `make build` green - [x] `make lint` clean (users package) - [x] `make coverage` 100% gate passes - [x] All 511 users package specs pass Closes bead bookshelf-xhqy on merge.
fix(users): add OIDCEnabled to failed-login error-render struct (bookshelf-xhqy)
All checks were successful
/ JS Unit Tests (pull_request) Successful in 29s
/ Lint (pull_request) Successful in 1m5s
/ E2E API (pull_request) Successful in 1m15s
/ Test (pull_request) Successful in 1m53s
/ Integration (pull_request) Successful in 2m6s
/ E2E Browser (pull_request) Successful in 2m13s
4e6a8dfe90
The POST /login error path (ErrInvalidCredentials) was re-rendering
login.html with an anonymous struct that had no OIDCEnabled field,
while the GET /login happy-path struct did include it. Since
login.html:46 references {{if .OIDCEnabled}}, any failed login with
OIDC enabled caused a template execute error → 500 instead of 401.

Fix: add OIDCEnabled bool + OIDCEnabled: d.OIDCEnabled to the
failed-login struct, matching the GET handler pattern.

Tests: update testAuthRenderer to reference .OIDCEnabled in the login
template stub (ensures future regressions are caught) and add two
new test contexts — OIDCEnabled=false and OIDCEnabled=true — that
assert the re-rendered login page carries the correct field value.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Author
Owner

Security Review — bookshelf-xhqy (bd-bookshelf-xhqy @ 4e6a8dfe)

Reviewed diff only; CI green, tests not re-run.


No security findings.

Auth semantics are unchanged:

  1. Status code preserved. w.WriteHeader(http.StatusUnauthorized) was already on that path before the fix; the diff does not touch it. Failed login still returns 401.
  2. No user-enumeration oracle. The error message is the static string "Invalid username or password." regardless of whether the username or password is wrong. No change introduced.
  3. No credential or timing leakage. OIDCEnabled is a server-side configuration boolean already exposed on the GET /login page. Populating it on the POST error path brings the error-render into parity with the initial page render — no new information surface.
  4. No redirect on failure. The failure path renders the template in-line; no http.Redirect or 302 is issued on bad credentials.
  5. JSON failure path also correct. The isJSONRequest branch (unchanged) returns {"error": "Invalid credentials"} with 401; OIDCEnabled is not included in that response (correct — it is only a template rendering concern).
  6. OIDCEnabled is non-secret. It controls only whether the OIDC button is rendered; no credential, key, or token is involved.
  7. Test coverage confirms intent. Two new It blocks assert 401 status and correct oidc= rendering for both OIDCEnabled=false (existing suite) and OIDCEnabled=true (new context), using the curried-function pattern.

REVIEW VERDICT: 0 blocker, 0 major, 0 minor

## Security Review — bookshelf-xhqy (bd-bookshelf-xhqy @ 4e6a8dfe) Reviewed diff only; CI green, tests not re-run. --- **No security findings.** Auth semantics are unchanged: 1. **Status code preserved.** `w.WriteHeader(http.StatusUnauthorized)` was already on that path before the fix; the diff does not touch it. Failed login still returns 401. 2. **No user-enumeration oracle.** The error message is the static string `"Invalid username or password."` regardless of whether the username or password is wrong. No change introduced. 3. **No credential or timing leakage.** `OIDCEnabled` is a server-side configuration boolean already exposed on the GET `/login` page. Populating it on the POST error path brings the error-render into parity with the initial page render — no new information surface. 4. **No redirect on failure.** The failure path renders the template in-line; no `http.Redirect` or `302` is issued on bad credentials. 5. **JSON failure path also correct.** The `isJSONRequest` branch (unchanged) returns `{"error": "Invalid credentials"}` with 401; `OIDCEnabled` is not included in that response (correct — it is only a template rendering concern). 6. **`OIDCEnabled` is non-secret.** It controls only whether the OIDC button is rendered; no credential, key, or token is involved. 7. **Test coverage confirms intent.** Two new `It` blocks assert 401 status and correct `oidc=` rendering for both `OIDCEnabled=false` (existing suite) and `OIDCEnabled=true` (new context), using the curried-function pattern. --- REVIEW VERDICT: 0 blocker, 0 major, 0 minor
Author
Owner

Code Review — PR #608 (bookshelf-xhqy)

Phase 0: DEMO Verification

No DEMO block required for a pure regression-fix bead; the test additions ARE the verification.

Phase 1: Spec Compliance

The bug: login.html uses {{.OIDCEnabled}} but the error-render struct in loginHandler did not carry that field → template execution panic → 500.

All login.html render sites audited (grep -rn "login.html" internal/ --include="*.go"):

Site File:Line Had OIDCEnabled before fix? After fix?
loginPageHandler (GET) handler.go:61 YES (lines 49-58 of main) unchanged
loginHandler ErrInvalidCredentials HTML branch (POST error) handler.go:116 NO — the bug YES — fixed by diff

The two other WriteHeader(http.StatusUnauthorized) sites in refreshHandler (lines 161, 168) return a bare 401 with no body and do NOT call Renderer.Render — not affected.

Phase 2: Code Quality

The test guard is sound. testAuthRenderer() now includes oidc={{.OIDCEnabled}} in the login template literal, so a missing OIDCEnabled field on the data struct would cause a template execution error and fail the test. The new OIDC=true context correctly:

  • Asserts 401 status.
  • Asserts oidc=true is present in the re-rendered body.

One minor style note: the new OIDCEnabled=true context places a full JustBeforeEach inside a nested Context that already has an outer JustBeforeEach. Ginkgo runs both in order (outer first, inner second), so the outer one does a redundant server spin-up + HTTP POST before the inner one overwrites resp/body. It works correctly — the It blocks see the right values — but it is slightly wasteful. No correctness impact.

defer localSrv.Close() inside JustBeforeEach is safe: bodyStr(resp) reads the response body before the defer fires, so no use-after-close.

No issues in the non-test code. The fix is minimal and correct.


[MINOR] internal/users/handler_test.go (~line 395) — new OIDCEnabled=true context duplicates the outer JustBeforeEach server spin-up
Outer JustBeforeEach fires first (builds srv with OIDCEnabled=false, posts, writes resp/body), then inner fires (rebuilds with OIDCEnabled=true, overwrites). The test asserts correctly but wastes one round-trip. Could be refactored to use a BeforeEach that sets OIDCEnabled=true on a Deps var and have the outer JustBeforeEach consume it. Not a blocker.

REVIEW VERDICT: 0 blocker, 0 major, 1 minor

## Code Review — PR #608 (bookshelf-xhqy) ### Phase 0: DEMO Verification No DEMO block required for a pure regression-fix bead; the test additions ARE the verification. ### Phase 1: Spec Compliance The bug: `login.html` uses `{{.OIDCEnabled}}` but the error-render struct in `loginHandler` did not carry that field → template execution panic → 500. **All `login.html` render sites audited** (`grep -rn "login.html" internal/ --include="*.go"`): | Site | File:Line | Had OIDCEnabled before fix? | After fix? | |------|-----------|----------------------------|------------| | `loginPageHandler` (GET) | `handler.go:61` | YES (lines 49-58 of main) | unchanged | | `loginHandler` ErrInvalidCredentials HTML branch (POST error) | `handler.go:116` | NO — the bug | YES — fixed by diff | The two other `WriteHeader(http.StatusUnauthorized)` sites in `refreshHandler` (lines 161, 168) return a bare 401 with no body and do NOT call `Renderer.Render` — not affected. ### Phase 2: Code Quality The test guard is sound. `testAuthRenderer()` now includes `oidc={{.OIDCEnabled}}` in the login template literal, so a missing `OIDCEnabled` field on the data struct would cause a template execution error and fail the test. The new OIDC=true context correctly: - Asserts 401 status. - Asserts `oidc=true` is present in the re-rendered body. One minor style note: the new `OIDCEnabled=true` context places a full `JustBeforeEach` inside a nested `Context` that already has an outer `JustBeforeEach`. Ginkgo runs both in order (outer first, inner second), so the outer one does a redundant server spin-up + HTTP POST before the inner one overwrites `resp`/`body`. It works correctly — the `It` blocks see the right values — but it is slightly wasteful. No correctness impact. `defer localSrv.Close()` inside `JustBeforeEach` is safe: `bodyStr(resp)` reads the response body before the defer fires, so no use-after-close. No issues in the non-test code. The fix is minimal and correct. --- [MINOR] `internal/users/handler_test.go` (~line 395) — new `OIDCEnabled=true` context duplicates the outer `JustBeforeEach` server spin-up Outer `JustBeforeEach` fires first (builds `srv` with `OIDCEnabled=false`, posts, writes `resp`/`body`), then inner fires (rebuilds with `OIDCEnabled=true`, overwrites). The test asserts correctly but wastes one round-trip. Could be refactored to use a `BeforeEach` that sets `OIDCEnabled=true` on a `Deps` var and have the outer `JustBeforeEach` consume it. Not a blocker. REVIEW VERDICT: 0 blocker, 0 major, 1 minor
zombor merged commit f1d2ebfc0e into main 2026-06-18 14:16:23 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
zombor/pergamum!608
No description provided.