/** * `@guuey/widget-auth` — mint end-user identity tokens for a guuey embeddable * widget, from your own backend. * * ```ts * import { signUserToken } from '@guuey/widget-auth'; * * const { token, expiresAtEpoch } = await signUserToken( * { userId: user.id, name: user.name, email: user.email }, * { appId: process.env.GUUEY_APP_ID!, appSecret: process.env.GUUEY_APP_SECRET! }, * ); * ``` * * ## One token, every surface * * The token is a plain RS256 JWT bound to your app's own issuer and audience — * `iss`, `sub` (= `userId`), `aud`, `iat`/`nbf`/`exp`, optional `name`/`email` — * with nothing widget-specific in it. Every guuey consumer verifies it the same * way and derives the same end-user identity from `userId`, so the widget * embed, a standalone agent page and **your own page built on * `@guuey/agent-client` / `@guuey/chat`** (`createWebAdapters({ getAccessToken })`) * all see one user with one history, memory and profile. "widget" in this * package's name is the name of the app's per-app issuer, not a limit on where * the token may be presented (guuey#206). * * ## What this package does NOT do, and why that matters * * It holds no key material and assembles no claims. The app's RSA private key * lives sealed in the platform's KMS and never leaves it, so this package is a * typed, validated HTTP call to the mint route and nothing more. * * In particular, **the token's time claims are assembled server-side and this * package must never re-implement them.** The signer sets `iat` and `nbf` * backdated 60 seconds — absorbing ordinary clock drift between the signer and * the agent pod, which verifies with zero clock tolerance — and derives `exp` * from the real current time, so the backdating buys verification headroom * without shortening the token's usable life. It also sets `iss` from the app's * canonical issuer string and `aud` from the app's own configuration. Sending * any of those from here is rejected outright by the signer's strict claim * parser, which allowlists exactly `sub`, `name` and `email` — so the rule is * enforced by the other end rather than merely stated here. * * Were the backdate re-implemented here anyway, the damage would not be a * shorter token: `exp` does not move, so nothing expires sooner. It would WIDEN * the acceptance window — `nbf` slides another 60s into the past, so the * skew margin silently stops being the 60 seconds it is documented as — and * `iat` would misstate the token's age to every consumer that reads it. * * ## The app secret is a SERVER-side credential * * `appSecret` authorizes minting an identity for *any* user of your app. It must * live only on your backend. If it reaches a browser — bundled into frontend * code, or because this package was called from the client — anyone who reads it * can mint a token for any of your users, which is the entire threat model this * design exists to prevent. That is why the widget asks *your* server for a * token rather than minting one itself. */ import { WidgetAuthAppNotConfiguredError, WidgetAuthConfigError, WidgetAuthCredentialError, WidgetAuthError, WidgetAuthNetworkError, WidgetAuthRequestError, WidgetAuthServiceError } from './errors.js'; export { WidgetAuthAppNotConfiguredError, WidgetAuthConfigError, WidgetAuthCredentialError, WidgetAuthError, WidgetAuthNetworkError, WidgetAuthRequestError, WidgetAuthServiceError, }; /** The end-user a token is being minted for. */ export interface WidgetUser { /** * Your stable identifier for this user — it becomes the token's `sub`, and the * platform derives the user's durable widget identity from it. * * It must be stable for the life of the account: it is what ties a returning * visitor to their existing conversations, memory and files. A value that * changes (a session id, an email that can be edited) silently orphans all of * it and the user reappears as a stranger. */ userId: string; /** Display name, shown in the widget. Optional. */ name?: string; /** Email, available to the agent. Optional. */ email?: string; } /** Where to mint, and as whom. */ export interface WidgetAuthConfig { /** The guuey app this token is for. */ appId: string; /** * The app secret from `guuey widget keys create`. **Server-side only** — see * the module docblock. */ appSecret: string; /** * The guuey API base URL, e.g. `https://api.guuey.com`. Falls back to the * `GUUEY_API_URL` environment variable. * * There is deliberately no compiled-in default: the API base differs per * environment, and a wrong built-in default would fail in a way that looks * like a credential problem rather than a configuration one. */ apiBaseUrl?: string; /** * Token lifetime in seconds, `1..3600`. Defaults to the service's 15 minutes. * * Shorter is safer — the token is a bearer credential held in a browser, and * the widget re-requests one from you when it expires. Prefer the default over * a long TTL; it is not a session length. */ ttlSeconds?: number; /** Aborts the request. */ signal?: AbortSignal; /** Override the HTTP client. Intended for tests. */ fetch?: FetchLike; } /** * A minted token, exactly as the mint route returns it — a hand mirror of * `@guuey-private/cli-wire`'s `AppUserTokenMintResponse`, pinned by * `wire-sync.test.ts`. */ export interface WidgetToken { /** The signed JWT — hand it to the widget, or to your own surface's `getAccessToken`. */ token: string; /** Unix epoch seconds at which `token` stops verifying. */ expiresAtEpoch: number; /** The issuer that signed it. */ issuer: string; /** The signing key's id. */ kid: string; } /** The request shape this package sends. */ export interface WidgetAuthRequestInit { method: string; headers: Record; body: string; signal?: AbortSignal; } /** The part of a `fetch` response this package reads. */ export interface WidgetAuthFetchResponse { status: number; json(): Promise; } /** * The HTTP seam. Structurally satisfied by the global `fetch`, so overriding it * is only needed in tests. */ export type FetchLike = (url: string, init: WidgetAuthRequestInit) => Promise; /** * Mint an end-user token for your widget. * * Resolves with a {@link WidgetToken}, or **throws** — always a * {@link WidgetAuthError} subclass, never a partially-formed result. See * `errors.ts` for the taxonomy and which failures a retry can fix. * * @param user the end-user this token identifies * @param config the app, its secret, and where to mint */ export declare function signUserToken(user: WidgetUser, config: WidgetAuthConfig): Promise; //# sourceMappingURL=index.d.ts.map