Accept the hub's shape of a panel token
Docs / docs (push) Successful in 31s
Playwright Tests / test-playwright (1, 2) (push) Successful in 5m15s
Playwright Tests / test-playwright (2, 2) (push) Successful in 2m16s
pre-commit / pre-commit (push) Failing after 2m19s
Test Backend / test-backend (push) Successful in 2m51s
Compose Smoke Test / test-compose (push) Successful in 39s
Playwright Tests / merge-reports (push) Successful in 1m12s

A screen paired through the portal asked the household for a fresh code on
every boot: it looks for the panel in a `panel` claim, and the hub — which
knows nothing of this installation's users — puts it in `sub` and says so with
`scope`. A local token carries no scope, so the two cannot be confused.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-27 16:15:48 +02:00
co-authored by Claude Opus 5
parent f855170562
commit 96cf1fc0c8
+14 -5
View File
@@ -11,10 +11,15 @@ import { apiToken, appPath, isPortal, openPortalSession } from "@/lib/portal"
/** /**
* The panel a credential in hand names, or "" when it names none. * The panel a credential in hand names, or "" when it names none.
* *
* A panel token carries the panel it was minted for; a user token and a * Two shapes say it, because two things mint one. This installation writes a
* malformed one both mean "not an adopted screen". Read rather than verified: * `panel` claim; the hub, which knows nothing of this installation's users,
* this only decides where to send the browser, and the server still judges the * puts the panel in `sub` and says so with `scope`. A local token carries no
* credential on the first call it makes there. * `scope` at all, so the two cannot be confused — and without the second shape
* a screen adopted through the portal named no panel, and asked the household
* for a fresh code on every single boot.
*
* Read rather than verified: this only decides where to send the browser, and
* the server still judges the credential on the first call it makes there.
*/ */
function pairedPanel(): string { function pairedPanel(): string {
try { try {
@@ -22,7 +27,11 @@ function pairedPanel(): string {
const claims = JSON.parse( const claims = JSON.parse(
atob(payload.replace(/-/g, "+").replace(/_/g, "/")), atob(payload.replace(/-/g, "+").replace(/_/g, "/")),
) )
return typeof claims.panel === "string" ? claims.panel : "" if (typeof claims.panel === "string") return claims.panel
if (claims.scope === "panel" && typeof claims.sub === "string") {
return claims.sub
}
return ""
} catch { } catch {
return "" return ""
} }