/** * The signed state of a PLATFORM-MINTED provider consent round (connections.md §3.5.3). * * §3.5.1 settled how an OAuth connection is authorized: the act originates in-scope, a * signed state token carries the proof, and the host-side callback effects the write * stamped with the principal from that state. The dashboard's connect link is one * instance of it. This is the second: a tenant admin working inside a VERTICAL — a * bookkeeping bureau connecting a client company, with no dashboard account and no * reason to have one — presses Connect on the vertical's own screen, and the vertical's * operation is the permission-checked act. * * **Why this claim carries no link row.** The dashboard's connect link is minted, mailed, * and clicked days later by someone else, so it is a row: single-use, revocable, and dead * when the minting admin loses access. A vertical-minted round is clicked in-session by * the person who just pressed the button, so the row would be state nobody reads. What * replaces it is the expiry — minutes, not a week — and the account leg of the connection * key: a replayed round re-consents the SAME company, which the store rotates in place * rather than duplicating (#1267). The consent code itself is single-use at the provider. * * **Why the kernel.** Two workers hold the halves — the control plane mints (it owns the * directory the vertical is re-derived from), the dashboard verifies (it owns the one * `redirect_uri` registered with the provider) — and a MAC that two deployments must agree * on is exactly the thing that must not be written twice. It sits beside `platform-call.ts` * for the same reason that does: both answer "did this really come from the platform". * * Web Crypto only, so this file is testable in Node and runs unchanged in workerd. */ /** Thrown when a claim cannot be minted — never when one fails to verify (that is `null`). */ export declare class ConnectStateError extends Error { constructor(message: string); } /** * The purpose label, and the `:v1` is the key-derivation version. Signing key is * HKDF(PLATFORM_SECRET, info = this), so a token minted here can never verify anywhere * else the platform secret is presented — `/internal/provision` and the two relays * compare the secret raw, and a MAC family shared with them would let a signature minted * for one be replayed as the other wherever the shapes overlap (the same trap #968 fixed * inside the dashboard). */ export declare const CONNECT_STATE_PURPOSE = "substrat-platform:connect-state:v1"; /** * What a platform-minted consent round proves. Every field is decided by the control * plane from its own directory — never taken from the caller — except `subjectRef`, * which is opaque to the platform by design (see below). */ export interface ConnectStateClaim { /** The tenant the connection lands under. */ tenantId: string; /** * The VERTICAL's scope. The callback upserts against this, and the connection store * re-derives the vertical from it a second time — so a claim cannot plant a * credential on a vertical other than the one that asked for the round. */ scopeId: string; /** The vertical slug, as the directory had it at mint. Carried for the log, not for trust. */ vertical: string; /** Provider slug (`fortnox`). */ provider: string; /** * The tenant principal whose in-scope `ctx.check` authorized the round — stamped on * the connection as `createdBy`, exactly as §3.5.1 requires. Not a dashboard member. */ principal: string; /** Where the browser is sent when the round settles; validated against the scope's own hostnames at mint. */ returnUrl?: string; /** * The vertical's OWN name for what is being connected — its client row's id. Opaque to * the platform, which never parses, stores, or acts on it; it is echoed back on the * return so a bureau holding two hundred outstanding rounds can attribute the one that * just landed without waiting to match on an organisation number. */ subjectRef?: string; /** Epoch ms. */ exp: number; } /** * `.`. * * Refuses an unset secret rather than signing with `''` — the same law * `assertPlatformCall` states: an unset secret is a failure, not a bypass. A token * signed under the empty string would verify for anyone who guessed that it was. */ export declare function signConnectState(platformSecret: string, claim: ConnectStateClaim): Promise; /** * The claim if the signature verifies under this deployment's platform secret and `exp` * is still ahead of `now`; `null` for everything else — a forgery, a token minted with * the raw secret, an expired round, a malformed string. One `null` for every failure on * purpose: the caller renders one refusal, and a distinguishable error is an oracle. * * An unset secret verifies nothing (never throws): a deployment holding no platform * secret has no platform-minted rounds to accept. */ export declare function verifyConnectState(platformSecret: string | undefined, token: string, now: number): Promise; //# sourceMappingURL=connect-state.d.ts.map