fix(auth): login/registration forms redirect again under hash CSP
The htmx login/registration partials answered success with an inline <script> that stashed the auth token and set window.location — refused by hash-mode script-src (no 'unsafe-inline'), so the user saw "Login successful! Redirecting..." and nothing else (reported from the home- page login form; /login was equally broken). The same pattern lived in collections.go (delete → redirect) and devices.go (token regen → reload, whose script tag was also unclosed). Replacement, per the AGENTS.md CSP rules: - auth.go login/registration: the partial now carries data-auth-token + data-redirect (redirect is path-only — open-redirect hygiene — and escaped); web/src/auth_forms.ts (new, armed from main.ts on every page) listens for htmx:afterSwap, stashes the token, and navigates. - collections.go delete: data-redirect, same listener. - devices.go token regen: htmx's native HX-Refresh response header. - The dead localStorage 'user'/'refreshToken' writes are dropped — no reader anywhere in web/src; only 'token' is load-bearing (19 modules). Tripwire extension: internal/middleware/csp_go_sources_test.go scans non-test Go sources for literal <script> tags and native on*= attributes (hand-written files only — templ codegen is owned by the .templ scan), verified to fire on a reintroduced probe. AGENTS.md CSP section notes the Go-emitted-partial vector. Verified live: home-page login form → /dashboard with token stashed and dashboard loading clean; /register form → /dashboard; collections and devices share the same mechanisms (data-redirect / HX-Refresh). Gates: make test (9 pkgs incl. both tripwires), vitest 155, Playwright 20, tsc/vp clean.
This commit is contained in:
1 parent
b7595c3011
commit
7c9e561e68
8 files changed
+124
-20
No files matched your search
@@ -0,0 +1,27 @@
|
||||
// CSP-safe post-swap navigation for server-emitted status partials.
|
||||
//
|
||||
// The login/registration/collection-delete htmx flows used to answer with an
|
||||
// inline <script> that stashed the auth token and redirected. Hash-mode CSP
|
||||
// (no 'unsafe-inline') refuses those scripts, so the server now emits plain
|
||||
// data attributes instead and this module performs the navigation:
|
||||
//
|
||||
// <div data-redirect="/dashboard" data-auth-token="…">Login successful!…</div>
|
||||
//
|
||||
// One delegated body listener covers every page (the main bundle loads
|
||||
// everywhere htmx does), so no per-page wiring. Devices' token regeneration
|
||||
// uses htmx's native HX-Refresh response header instead and needs nothing
|
||||
// here.
|
||||
//
|
||||
// Note: only the token is stashed. The old script also wrote localStorage
|
||||
// 'user'/'refreshToken', but nothing in web/src ever read them.
|
||||
|
||||
export function armAuthFormRedirects() {
|
||||
document.body.addEventListener("htmx:afterSwap", (event) => {
|
||||
const detail = (event as CustomEvent).detail as { target?: HTMLElement } | undefined;
|
||||
const el = detail?.target?.querySelector("[data-redirect]");
|
||||
if (!el) return;
|
||||
const token = el.getAttribute("data-auth-token");
|
||||
if (token) localStorage.setItem("token", token);
|
||||
window.location.href = el.getAttribute("data-redirect") ?? "/";
|
||||
});
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import "./docs";
|
||||
import "./header";
|
||||
import "./index";
|
||||
import "./image_fallback";
|
||||
import { armAuthFormRedirects } from "./auth_forms";
|
||||
import "./library-switcher";
|
||||
import "./linking";
|
||||
import "./login";
|
||||
@@ -38,4 +39,5 @@ import "./unlinked_books";
|
||||
import "./websocket";
|
||||
import "./woodPanelingInit";
|
||||
|
||||
armAuthFormRedirects();
|
||||
Alpine.start();
|
||||
Reference in new issue
Block a user