diff --git a/frontend/src/routes/panel.index.tsx b/frontend/src/routes/panel.index.tsx index de8db01..531d2f3 100644 --- a/frontend/src/routes/panel.index.tsx +++ b/frontend/src/routes/panel.index.tsx @@ -11,10 +11,15 @@ import { apiToken, appPath, isPortal, openPortalSession } from "@/lib/portal" /** * 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 - * malformed one both mean "not an adopted screen". 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. + * Two shapes say it, because two things mint one. This installation writes a + * `panel` claim; the hub, which knows nothing of this installation's users, + * puts the panel in `sub` and says so with `scope`. A local token carries no + * `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 { try { @@ -22,7 +27,11 @@ function pairedPanel(): string { const claims = JSON.parse( 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 { return "" }