feat(web): login gate for api.auth.mode - #859
Conversation
users table (operator/viewer, argon2id PHC hashes) in state, plus go/internal/localauth: constant-time password verification at OWASP argon2id parameters and in-memory bearer sessions with expiry and per-user revocation. Sessions are memory-only on purpose — restart logs everyone out, and no session secret touches the database. API enforcement comes separately; nothing changes for existing installs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Sanjin Naidu <sanjin@sanrowconsulting.com>
api.auth.mode open|local_trust|required layered outside the existing SecureMutations gate: open is byte-identical to today, local_trust gates only non-local requests (viewer reads, operator mutations, bearer token still valid for automation), required gates everything but login/health/static. ftw user CLI bootstraps accounts on the box; startup refuses login modes with zero enabled operators. Every mutation attempt lands in audit_log with its principal, served at GET /api/audit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Sanjin Naidu <sanjin@sanrowconsulting.com>
Overlay asks /api/auth/session on load; open mode or a live session removes it instantly so existing dashboards render untouched. Login- required modes block the app with a themed sign-in form (uniform wrong-credentials text mirroring the API's no-oracle rule); success reloads so every component fetches with the session cookie. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Sanjin Naidu <sanjin@sanrowconsulting.com>
5e6943e to
0bf8b1c
Compare
|
The gate does not appear for an unauthenticated user in the current stack. #858 protects Make session discovery return an unauthenticated payload, or handle 401 as login-required without hiding the gate. After rebasing onto the app API-boundary work now on |
miravoss26
left a comment
There was a problem hiding this comment.
Reviewed the last commit (feat(web): login gate for api.auth.mode) per the stacked-PR note — the <ftw-login-gate> element, its pure decision logic (login-math.js), and the unit tests.
Correctness: shouldShowLogin/loginErrorText are small and match the design doc exactly (open/authenticated → never block, 401 → show, fetch failure → don't block and let the app's own error UI own it). The component logic reads right: checks /api/auth/session on connect, removes itself when no gate is needed, reloads on successful login so every other component picks up the session cookie from a clean state.
Security: clean — errors render via err.textContent, never innerHTML, so there's no XSS surface even though the error text technically comes from a server response. No dynamic values get interpolated into the template. SameSite=Strict on the session cookie (from #858) covers CSRF for the login POST reasonably well from this side.
Nothing to block on in this commit. Its actual security posture inherits directly from #858 underneath it, though — I flagged two real issues there (/api/audit unauthenticated in open mode, and a login timing side-channel that undercuts the "no username oracle" comment this file's loginErrorText also references). Worth keeping those linked so this PR isn't approved as if the chain below it is clean.
One nice-to-have, not a blocker: the password field isn't cleared after a failed attempt, so a shoulder-surfer gets a second read of it sitting in the input — minor UX/privacy nit, not worth holding the PR for.
|
Thank you @Sanjin-Maker for completing the prototype through the browser surface. We are closing this branch with #857 and #858. The gate currently disappears on backend failure or 401 while the dashboard can remain visible and its API calls fail. Any future UI also requires human browser review for open, required, authenticated, unauthenticated, expired-session and backend-unavailable states. The UI should come only after a written auth decision and an accepted API contract. |
Summary
Stacked on #858 (auth enforcement) — review the last commit until the chain lands. @erikarenhill for /web/. Completes the commercial-readiness auth slice.
<ftw-login-gate>mounts first in<body>: asksGET /api/auth/session; onmode: openor an authenticated session itremove()s itself immediately — zero visual or behavioral change for every existing install. On login-required modes it covers the app with a themed sign-in form (shadow styles from:roottokens, both themes, no dependencies).login-math.js) undernode --test.Not yet screenshot-verified against a live
required-mode stack; markup follows the house component patterns and the decision logic is unit-tested.Verification
npm test: 260 pass (7 new). DCO signed; minor changeset.🤖 Generated with Claude Code