/** * Packed per-tenant roles claim — the token-side half of * `createTenantUserScopesMiddleware`'s claims-first fast path. * * Multi-tenant apps assign users one role PER TENANT (a membership, * a staff assignment, a seat, …). A single Cognito custom attribute * can carry all of them: * * custom:tenantRoles = ":|:|…" * * Contract (both halves matter — read the write-path rules!): * * READ — the middleware unpacks the claim from the verified ID * token and grants `scopesByRole[role]` for the current tenant * with NO store round-trip. A missing tenant falls back to the * app's `tenantUser` resolver, so a stale-but-too-narrow claim * can never lock a user out — it only costs the query the claim * would have saved. * * WRITE — the app re-derives the FULL pack from its tenant-user * store and writes it (e.g. `adminUpdateUserAttributes`) on every * tenant-user mutation: sign-in upsert, approve, role change, * suspend / unsuspend, leave. Two rules: * 1. Pack ACTIVE tenant users only (approved, not suspended, * not deleted). "Active" is decided at write time — the * claims path deliberately has no `isActive` hook. * 2. Always recompute from the store; never string-edit the * previous value. Recomputing is self-healing. * * Staleness — the honest trade-off vs. the per-request store lookup: * attributes are baked into tokens at issue time, so a change lands * on the caller's NEXT token refresh (≤ the ID-token TTL, 1h by * default). GRANTS take effect immediately anyway thanks to the * claim-miss fallback; REVOCATIONS (suspend, demote) keep honoring * the old role until refresh. Apps for which that window is * unacceptable should not enable the claims path. * * Size — Cognito caps custom attribute values at 2048 chars * ({@link TENANT_ROLES_MAX_LENGTH}), ≈ 40 UUID-keyed entries. * {@link packTenantRoles} keeps entries in the given order and drops * whatever doesn't fit; dropped tenants degrade to the fallback query. * Order entries by likelihood of use (e.g. most recently active). */ /** * Default claim name. The pool must declare a `tenantRoles` custom * attribute (`customAttributes: ['tenantRoles', …]` on the auth * intent in vk.config.ts) — Cognito prefixes it to * `custom:tenantRoles` in JWTs. * * Deliberately NOT `custom:role` (the conventional login-tenant role * claim, per-user and single-valued) and NOT `custom:roles` * (`@venturekit/auth`'s session helpers parse that as a flat * comma-separated role list). */ export declare const TENANT_ROLES_CLAIM = "custom:tenantRoles"; /** Cognito's hard cap on a custom attribute value. */ export declare const TENANT_ROLES_MAX_LENGTH = 2048; /** One packed entry: which role the user holds in which tenant. */ export interface TenantRoleEntry { tenantId: string; role: string; } /** * Pack tenant-role entries into the claim value, first-entry-wins on * duplicate tenants, dropping trailing entries that would push the * value past {@link TENANT_ROLES_MAX_LENGTH} (or a custom `maxLength` * in tests). Returns `''` for no entries — write the empty string * anyway; it must OVERWRITE the previous pack when the user's last * tenant goes away. * * Throws on entries the codec cannot round-trip (empty fields or * separator characters inside ids/roles) — those indicate a bug in * the caller's derivation query, not user input. */ export declare function packTenantRoles(entries: readonly TenantRoleEntry[], maxLength?: number): string; /** * Decode a packed claim into a `tenantId → role` map. Fail-closed on * anything unexpected: a non-string claim, malformed segments, or * duplicate tenants (first wins) silently contribute nothing — a * garbled claim must never grant scopes, and the middleware's * fallback query covers the affected tenant. */ export declare function unpackTenantRoles(raw: unknown): ReadonlyMap; //# sourceMappingURL=tenant-roles-claim.d.ts.map