/** * OAuth 2.1 / PKCE helpers and JWT-aware refresh. * * `jwtRefresh(opts)` peeks the JWT exp claim on every request and * proactively refreshes the token *before* a 401 round-trip. Concurrent * requests with an expired token collapse onto a single refresh call. * * `createPkcePair()` generates a code verifier + S256 challenge per RFC * 7636 §4.1–4.2; `exchangePkceCode(...)` performs the standard token * exchange (RFC 6749 §4.1.3) with the verifier attached. */ import type { Misina, MisinaContext, MisinaPlugin } from "../types.mjs"; export interface JwtRefreshOptions { /** Async function that returns a new token. Concurrent calls collapse. */ refresh: () => string | Promise; /** Read the current token (so we can decode it and decide). */ getToken: () => string | undefined | Promise; /** * Refresh this many milliseconds before the JWT's `exp` claim. Lets * the call site tolerate clock skew and a slow network. Default: * 30_000 (30 s). */ expiryWindowMs?: number; /** * Reject when refresh returns the same token (likely a misconfigured * IdP). Default: true. */ rejectIfUnchanged?: boolean; /** * Override the predicate for "this request needs auth at all". Default: * any request whose Authorization header is set. */ shouldRefresh?: (ctx: MisinaContext) => boolean; } /** * Peek the JWT in the current `Authorization: Bearer ` header and * preemptively refresh it when it would expire within `expiryWindowMs`. * Concurrent expired requests share one refresh (mutex). * * Refresh runs in `beforeRequest`, so the *current* request goes out with * the new token — no extra 401 round-trip. */ export declare function jwtRefresh(opts: JwtRefreshOptions): MisinaPlugin; /** * Decode the `exp` claim from a JWT without verifying its signature. The * server is the only entity that can verify; the client uses this purely * to decide when to refresh. Returns null if the input doesn't look like * a JWT, the payload isn't JSON, or `exp` is missing. */ export declare function peekJwtExp(token: string): number | null; export interface PkcePair { /** Random URL-safe verifier (RFC 7636 §4.1). 43–128 chars. */ verifier: string; /** Base64URL(SHA-256(verifier)), per S256 method (RFC 7636 §4.2). */ challenge: string; /** Always `'S256'` — we don't ship plain method (RFC 8252 §8.1). */ method: "S256"; } /** * Generate a fresh PKCE verifier + S256 challenge pair. The verifier is * 32 bytes of crypto-grade randomness, base64url-encoded (43 chars), and * the challenge is base64url(SHA-256(verifier)). * * Caller stores the verifier (server-side session, sessionStorage, or an * encrypted cookie) until the redirect comes back, then attaches it to * the token exchange call via `exchangePkceCode`. */ export declare function createPkcePair(): Promise; export interface PkceExchangeOptions { /** RFC 6749 token endpoint URL. */ tokenEndpoint: string; /** OAuth client id. */ clientId: string; /** Redirect URI used in the original authorization request. */ redirectUri: string; /** Authorization code returned by the IdP. */ code: string; /** Verifier from the original `createPkcePair()` call. */ verifier: string; /** Optional client secret for confidential clients. */ clientSecret?: string; } export interface OAuthTokenResponse { access_token: string; token_type: string; expires_in?: number; refresh_token?: string; scope?: string; id_token?: string; [key: string]: unknown; } /** * Exchange an authorization code for tokens (RFC 6749 §4.1.3) with the * PKCE verifier attached (RFC 7636 §4.5). Posts * `application/x-www-form-urlencoded` to `tokenEndpoint` and returns * the parsed token response. */ export declare function exchangePkceCode(misina: Misina, options: PkceExchangeOptions): Promise;