Files
hanzo.ai/e2e/credential.ts
zeekayandHanzo Dev dff1d5f1e3 A signed-in spec starts past the organization picker
The one seeder in e2e/credential.ts now marks the browser as the bearer's
and chooses the first organization on the token, the way a returning
person's browser already has; the three specs that copied the seeder use
it. The other spec edits say what the site does now: money is at
pay.hanzo.ai, the header has no products door, ledger is the private
repo, a fresh board carries four projects, and the landing composer is
measured after it is scrolled into view.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 03:07:17 -07:00

53 lines
2.0 KiB
TypeScript

import { readFileSync } from 'node:fs'
/**
* A credential this machine holds, or nothing.
*
* The rooms only exist for somebody signed in, and signing in is IAM's job
* rather than a spec's — so these carry a token a local cloud minted. Absent is
* a state and not a failure, and so is EXPIRED: a token that ran out an hour ago
* makes every org-scoped read answer 403, which shows up as an empty roster and
* reads exactly like a regression in the code. One rule, in one place, so a spec
* cannot decide it differently.
*/
export function credential(): string | null {
let raw: string
try {
raw = readFileSync('/tmp/hz.tok', 'utf8').trim()
} catch {
return null
}
if (!raw) return null
try {
const part = raw.split('.')[1].replace(/-/g, '+').replace(/_/g, '/')
const { exp } = JSON.parse(Buffer.from(part, 'base64').toString())
// A minute of margin: a token that dies mid-run is the same problem.
if (typeof exp === 'number' && exp * 1000 < Date.now() + 60_000) return null
} catch {
// Unreadable claims are not a reason to refuse a token the cloud may accept.
}
return raw
}
/**
* Seeds the session a signed-in room needs. Pass it the token.
*
* The browser is the bearer's (`hanzo:who`, or the first read wipes every key
* as a stranger's) and the first organization on the token is already chosen,
* as a person who has been here before — the rooms open past the picker.
*/
export function signedIn(token: string) {
return (t: string) => {
localStorage.setItem('hanzo_iam_access_token', t)
localStorage.setItem('hanzo_iam_id_token', t)
const c = JSON.parse(atob(t.split('.')[1].replace(/-/g, '+').replace(/_/g, '/')))
localStorage.setItem('hanzo_iam_expires_at', String(c.exp * 1000))
localStorage.setItem('hanzo:who', c.sub)
const org = c.orgs?.[0]?.org ?? c.owner
if (org) {
localStorage.setItem('hanzo_iam_current_org', org)
localStorage.setItem('hanzo:org:picked', '1')
}
}
}