Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion internal/handlers/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -1131,8 +1131,26 @@ func (h *AuthHandler) consumeOAuthState(ctx context.Context, state string) bool
// remember to escape — defense in depth.
func renderAuthError(c *fiber.Ctx, status int, headline, detail string) error {
c.Set("Content-Type", "text/html; charset=utf-8")
// BUG-API-404 (QA 2026-05-29): the OAuth / magic-link callback HTML
// is a per-request rendering of session-bound state — a back-button,
// browser-history-restore, or service-worker re-fetch must NOT replay
// it (the underlying token has been consumed or expired). Without
// Cache-Control, the body could be re-served by the browser fetch
// cache or any intermediary, which both leaks the "you tried this
// link" UX state across sessions AND, in the success-redirect cousin
// of this surface, would re-set the exchange cookie. `no-store`
// (RFC 9111 §5.2.2.5) is the strongest stop-cache directive and
// matches the contract every other auth-result surface in the api
// already follows.
c.Set(fiber.HeaderCacheControl, "no-store")
// BUG-API-257 (QA 2026-05-29): the <html> element used to ship with
// no `lang` attribute. WCAG 3.1.1 "Language of Page" requires a
// programmatically determinable primary language; assistive tech
// (VoiceOver, NVDA) falls back to the OS locale otherwise, mis-
// pronouncing English copy in non-English locales. `lang="en"` is
// the correct value for the static English-only copy below.
body := fmt.Sprintf(`<!DOCTYPE html>
<html>
<html lang="en">
<head><meta charset="UTF-8"><title>Sign-in error</title></head>
<body style="font-family:sans-serif;max-width:480px;margin:48px auto;padding:24px;color:#111;">
<h2>%s</h2>
Expand Down
12 changes: 12 additions & 0 deletions internal/handlers/auth_helpers_coverage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,12 +263,24 @@ func TestAuth_RenderAuthError_StatusAndContentType(t *testing.T) {
defer resp.Body.Close()
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
assert.Contains(t, resp.Header.Get("Content-Type"), "text/html")
// BUG-API-404 (QA 2026-05-29): the OAuth / magic-link callback HTML
// is per-request session-bound state — back-button or service-worker
// re-fetch must NOT replay it. Pin Cache-Control: no-store so a
// future regression that drops the c.Set call fails before merge.
assert.Equal(t, "no-store", resp.Header.Get("Cache-Control"),
"BUG-API-404: renderAuthError must stamp Cache-Control: no-store on every callback HTML response")
buf := make([]byte, 1024)
n, _ := resp.Body.Read(buf)
body := string(buf[:n])
assert.Contains(t, body, "<title>Sign-in error")
assert.Contains(t, body, "Hello")
assert.Contains(t, body, "Detail")
// BUG-API-257 (QA 2026-05-29): WCAG 3.1.1 — the <html> element MUST
// carry a lang attribute so assistive tech (VoiceOver, NVDA) doesn't
// fall back to the OS locale and mispronounce English copy. Pin the
// value here so a future revert to the bare <html> fails before merge.
assert.Contains(t, body, `<html lang="en">`,
"BUG-API-257: renderAuthError HTML must include lang=\"en\" on the <html> element (WCAG 3.1.1)")
}

// SEC-API FINDING-23 regression: renderAuthError must HTML-escape both
Expand Down
Loading