fix(pwa): unbreak sign-out, and stop echoing $PUBLIC_ORIGIN in the CLI hint (#108)

Two bugs on the dashboard, both fixed by handing appBar/CLI_HINT the request.

Sign-out was broken for everyone. csrfGuard rejects any POST whose _csrf does
not match the mc_csrf cookie, and /auth/logout is a POST that is not on the
exempt list, but the sign-out form carried no hidden field -- every click
answered 403 "bad csrf token". appBar now takes the request rather than the
user, because it needs the token as well as the identity. The field is written
out instead of reusing csrfInput(): html.mjs is the view layer and imports
nothing, and pulling in session.mjs would drag the database driver with it.

The "Connect the CLI" snippet still printed $PUBLIC_ORIGIN, so users on
app.logicsrc.com were told to point LOGICSRC_API at the generated Railway
hostname. #105 added requestOrigin() for exactly this and fixed the device-flow
URLs; the dashboard hint was missed. It now follows the request too, which is
not a hardcode swap -- the same deployment answering on its Railway hostname
still self-describes correctly.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Anthony Ettinger 2026-07-30 10:56:36 -07:00 committed by GitHub
parent 3649f78fb6
commit 898d5503b8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 56 additions and 8 deletions

View file

@ -101,14 +101,22 @@ ${head}
</html>`;
}
export function appBar(user) {
// Takes the request, not just the user: signing out is a POST, and csrfGuard
// rejects any POST whose _csrf does not match the cookie. Without the hidden
// field here every "Sign out" click answered "bad csrf token".
//
// The field is written out rather than imported from session.mjs on purpose --
// this module is the view layer and has no imports, and pulling in session.mjs
// would drag the database driver along with it.
export function appBar(req) {
const user = req?.user;
return `<header class="bar"><div class="wrap bar-inner">
<a class="brand" href="/"><span class="mark">LS</span>LogicSRC<span class="app">credentials</span></a>
<div class="bar-right">
${user
? `<span class="mono faint" style="font-size:.78rem">${esc(user.email || user.display_name || "signed in")}</span>
<a class="btn" href="/settings">Settings</a>
<form method="post" action="/auth/logout" style="margin:0"><button class="btn">Sign out</button></form>`
<form method="post" action="/auth/logout" style="margin:0"><input type="hidden" name="_csrf" value="${esc(req?.csrfToken)}"><button class="btn">Sign out</button></form>`
: `<a class="btn acid" href="/">Sign in</a>`}
</div>
</div></header>`;