mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 04:20:39 +00:00
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]>
53 lines
2.0 KiB
TypeScript
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')
|
|
}
|
|
}
|
|
}
|