import type { AuthLogger, AuthSession, Es256PrivateJwk, Es256PublicJwk, JwtConfig } from '../types.js'; export declare const BASE64URL_REGEX: RegExp; /** * The `purpose` claim stamped into every session token (HS256 and ES256 * alike) and REQUIRED by `verifySessionToken` and the federated consumer * handle (`createFederatedAuthHandle`). Purpose binding lives in the * primitive, not in each caller's claim-shape check: two token kinds signed * with the same `jwt.secret` (a session token and, say, the pending-2FA * handle) can never be accepted for each other's purpose, whatever claims * they carry. The value is wire contract across app boundaries (IdP ↔ * federated consumers) — never change it without upgrading both sides * (IdP first; see docs/AUTH.md → Federated Identity). */ export declare const SESSION_TOKEN_PURPOSE = "session"; /** * Hard input-length cap applied by `verifySessionToken`, `verifySignedToken` * and the federated consumer handle BEFORE any splitting or parsing. Tokens * this package mints are well under 1 KB, and browsers cap a cookie at ~4 KB * (RFC 6265 minimum) — so 8 KB, double the cookie ceiling, can never reject a * legitimate cookie-borne token while keeping adversarial input finite for a * consumer that applies these verifiers to unbounded non-cookie input * (headers, request bodies). Belt-and-suspenders: the parse path is linear * and early-rejecting even without it. */ export declare const MAX_TOKEN_LENGTH = 8192; export declare function es256Verify(payload: string, signature: string, publicKey: { x?: string; y?: string; }): Promise; /** * RFC 7638 JWK thumbprint of a P-256 key: SHA-256 over the canonical JSON of * the required public members (`crv`, `kty`, `x`, `y` in lexicographic order, * no whitespace), base64url-encoded. Deterministic — the same key always maps * to the same value, and private and public JWK of one pair agree (only public * members feed the hash). This is the default `kid` for ES256 session tokens * and for the keys served by `createJWKSHandler`; use it to derive the `kid` * of a retiring key when building `previousPublicKeys` by hand. */ export declare function computeJwkThumbprint(jwk: JsonWebKey): Promise; /** * Generate a fresh ES256 (ECDSA P-256) key pair for `jwt.algorithm: 'ES256'`, * as JWKs ready for the config: `privateKey` goes into `jwt.signingKey` (keep * it secret); `publicKey` is what `createJWKSHandler` serves for it and what a * retiring key contributes to `jwt.previousPublicKeys`. Both JWKs are stamped * with the same `kid` — the RFC 7638 SHA-256 thumbprint, deterministic for the * key, so recomputing it later always yields the same id. * * Run this once in a setup script and store the result in your secret manager * — never on boot: a fresh key per process would invalidate every live * session and desynchronize the JWKS consumers rely on. */ export declare function generateES256KeyPair(): Promise<{ privateKey: Es256PrivateJwk; publicKey: Es256PublicJwk; kid: string; }>; /** * The `kid` stamped into new ES256 session tokens and used for the active key * in the JWKS document — one resolution shared by `createSessionToken` and * `createJWKSHandler` so the two can never drift: `keyId` when configured, * else the signingKey's own `kid` (stamped by {@link generateES256KeyPair}), * else the RFC 7638 thumbprint computed on the fly (identical to the stamped * value for generated keys). */ export declare function resolveActiveKid(config: JwtConfig): Promise; /** * Fail loud at wiring time on an unusable JWT config — same posture as * `assertReposMatchConfig`, and called from the same two entry points * (`createAuthDeps` and `createAuthHandle`) so neither path can silently mint * dead tokens: * * - `algorithm: 'ES256'` without a usable private P-256 `signingKey` → throw * (every later login would throw at sign time anyway — surface it at * construction instead). * - a `previousPublicKeys` entry that is not a public P-256 JWK with a `kid` * → throw (verification selects by `kid`; an entry without one is dead * config that silently fails to verify the tokens it was added for). * - a `previousPublicKeys` entry carrying the private scalar `d` → loud * error-level warning, once: only public members are ever used or * published, but private key material does not belong in a public-key list. * - `signingKey`/`previousPublicKeys` set while `algorithm` is not `'ES256'` * → loud error-level warning, once: the keys are ignored and sessions stay * HMAC-signed, which almost certainly means `algorithm: 'ES256'` was * forgotten. * - `cookieSameSite: 'none'` without `cookieSecure` → throw (browsers reject * the pair; see the note at the check itself). */ export declare function assertJwtConfigValid(config: JwtConfig, logger?: AuthLogger): void; export declare function createSessionToken(payload: AuthSession, config: JwtConfig): Promise; export declare function verifySessionToken(token: string, config: JwtConfig, logger?: AuthLogger): Promise | null>; /** * Sign an arbitrary claims object as a compact HS256 JWT, stamping `iat`/`exp` * and the mandatory `purpose` claim. The generic counterpart to * {@link createSessionToken} — for short-lived, single-purpose tokens (e.g. * the pending-2FA handle, `purpose: '2fa-pending'`) that ride in their own * cookie and are read back with {@link verifySignedToken} under the SAME * purpose. The purpose is the token's type: two token kinds signed with the * same secret can never be accepted for each other (`purpose` is a reserved * claim — passing it inside `claims` throws; `'session'` is taken by * {@link SESSION_TOKEN_PURPOSE}). It is deliberately HMAC-based under * **every** `jwt.algorithm` — these tokens never leave the deployment, so * asymmetric verification buys nothing, which is why `jwt.secret` stays * required even in ES256 mode. No new key material is introduced; pass * `config.jwt.secret`. `expiresInSeconds` should be small (minutes). */ export declare function createSignedToken(claims: Record & { purpose?: never; }, secret: string, expiresInSeconds: number, purpose: string): Promise; /** * Verify a {@link createSignedToken} token: timing-safe signature check plus a * **mandatory, in-date `exp`** plus a **mandatory `purpose` match** — a token * whose `purpose` claim is missing or differs from the `purpose` argument is * rejected in the primitive, whatever its other claims say. Returns the * decoded claims on success, else `null` (oversized, malformed, bad * signature, missing/expired `exp`, wrong purpose) — never throws on token * input (an invalid `purpose` ARGUMENT throws: that is API misuse, not a bad * token). A valid signature+purpose still does not vouch for claim shape: the * caller SHOULD keep checking the domain claims it expects (e.g. a string * `sub`), exactly as `verifySessionToken` validates its claim shape. */ export declare function verifySignedToken>(token: string, secret: string, purpose: string): Promise<(T & { purpose: string; iat: number; exp: number; }) | null>;