API index and search · Build metadata
Supporting declarations
packages/room/src/token.ts. These declarations explain referenced types. Only package-page symbols are package exports.
RoomTokenPayload
/**
* The room's **self-authorizing signed lease token** (RINDLE-REALTIME-DESIGN.md §10.1).
*
* The API server is the authority (§4): it authenticates the user, resolves the named
* query to an approved AST, and signs this token. The token then IS the lease — the
* room materializes on first presentation, so no pre-placement `/materialize` control
* call ever touches it (the §10.1 inversion; on the DO shell the Worker verifies the
* same signature statelessly before `get(id)`).
*
* Shape: `rt1.<base64url(payload)>.<base64url(hmac-sha256(prefix.payload))>` with
* payload `{ v: 1, doc, ast, sub, iat, exp, kid }`:
*
* - `doc` — the room/document id the token authorizes (a token for one room presented
* to another is refused);
* - `ast` — the approved wire `Ast` (the client never composes this; §4's "ASTs never
* cross the public wire" holds because the token is opaque TO THE CLIENT — it carries
* it, it cannot mint or alter it);
* - `sub` — the subject (user id): the revocation key (§4.1);
* - `iat`/`exp` — issued-at / expiry, ms epoch. Renewal is re-authorization: clients
* obtain a fresh token through the API server, never extend this one. `iat` is what
* lets a revocation refuse pre-revocation tokens while a genuine re-grant (a newer
* token) passes immediately;
* - `kid` — which shared secret signed it (rotation).
*
* HMAC via WebCrypto (`crypto.subtle`) so the exact same code verifies in Node (the
* test shell) and in a Cloudflare Worker/DO (P4) — no `node:crypto` import.
*/
export interface RoomTokenPayload {
v: 1;
doc: string;
ast: unknown;
sub: string;
iat: number;
exp: number;
kid: string;
/** A short fingerprint of the room's compiled scope specs at mint time
* ({@link scopeSpecsHash}). Advisory, not a credential: the room's §3.3 gate is the
* contract regardless. It lets the shell detect SCOPE SKEW — a room profile edited
* while a room is already live arms the gate with the OLD specs (a one-shot at boot)
* while fresh leases prove against the NEW ones, so every routed write silently
* deopt-loops. Optional so a pre-stamp api-server / older token still verifies. */
scopesHash?: string;
}MintRoomTokenOptions
export interface MintRoomTokenOptions {
doc: string;
ast: unknown;
/** The subject (user id) this token authorizes — the §4.1 revocation key. */
sub: string;
/** Key id + its secret (utf-8; give every room the same `keys` map). */
kid: string;
key: string;
/** Expiry, ms from `now`. Keep short (minutes) — the §4.1 TTL backstop. */
ttlMs: number;
/** Mint time; defaults to `Date.now()`. Injectable for tests. */
now?: number;
/** The room's {@link scopeSpecsHash} for the profile this lease serves — stamped so the
* shell can flag scope skew (see {@link RoomTokenPayload.scopesHash}). Omit to not stamp. */
scopesHash?: string;
}scopeSpecsHash
/** A stable short fingerprint of the compiled scope specs — the scope-skew tripwire.
* NOT security (the gate re-proves every write): FNV-1a-32 over the {@link canonicalJson}
* form, 8 hex chars. Both the api-server (stamping the lease token) and the room shell
* (hashing the boot-wire scopes it armed the gate with) run this over the SAME compiler
* output, so an unchanged profile ⇒ equal hash and a profile edited under a live room ⇒
* mismatch. A collision only costs a missed diagnostic, never correctness. Accepts either
* wire's spec array (`RoomScopeSpec[]` / `RoomTableSpec[]` — structurally identical). */
export declare function scopeSpecsHash(specs: readonly unknown[]): string;mintRoomToken
/** Sign a room lease token. This runs on the API-server side (or a test playing it). */
export declare function mintRoomToken(opts: MintRoomTokenOptions): Promise<string>;RoomTokenError
/** Why a token was refused. The `reason` is terse and safe to echo in a queryError. */
export declare class RoomTokenError extends Error {
readonly reason: string;
constructor(reason: string);
}VerifyRoomTokenOptions
export interface VerifyRoomTokenOptions {
/** The room's own doc id — a token for any other doc is refused. */
doc: string;
/** kid → shared secret. Unknown kids are refused (never "try them all"). */
keys: Record<string, string>;
/** Verification time; defaults to `Date.now()`. Injectable for tests. */
now?: number;
}verifyRoomToken
/**
* Verify a token's signature and claims; returns the payload (with the approved AST)
* or throws {@link RoomTokenError}. Signature is checked FIRST — no claim is trusted
* (not even `kid`'s existence beyond the key lookup) before the MAC passes.
*/
export declare function verifyRoomToken(token: string, opts: VerifyRoomTokenOptions): Promise<RoomTokenPayload>;