/** * CSRF protection middleware for the server module. * * Implements the OWASP double-submit-cookie pattern. A per-client secret is * stored in a cookie; the matching token must be echoed back in a request header * (or form field) on state-changing requests. When a server `secret` is * supplied the token is HMAC-signed (signed double-submit). * * Signing alone does not stop cookie injection: an attacker who can set a * cookie for the victim (a sibling subdomain, or a MITM on a non-`__Host-` * cookie) plants the secret from a validly signed pair minted for themselves. * So in signed mode, when the `session()` middleware runs first and the request * carries a stored session, the secret is kept in that session instead of a * cookie (synchronizer token), which binds the token to the session and leaves * nothing to inject. Visitors without a session keep the signed cookie. * * Pairs with the `security` module: CSRF guards request *integrity* while * `sanitizeHtml()` / Trusted Types guard *output*. Use both for defense in depth. * * @module bquery/server */ import type { ServerCookieOptions, ServerContext, ServerMiddleware } from './types.js'; /** Options for the {@link csrf} middleware. */ export interface CsrfOptions { /** * Optional signing secret(s). When provided, tokens are HMAC-signed (signed * double-submit). Pass an array to rotate secrets. When omitted, plain * double-submit is used (token equals the cookie secret). */ secret?: string | readonly string[]; /** * Cookie name holding the per-client secret. Defaults to `'__Host-bq.csrf'` * when the cookie is `Secure`, has `path: '/'` and no `domain` (the * default attributes), and to `'bq.csrf'` otherwise. The `__Host-` prefix * makes browsers refuse the cookie from a sibling subdomain or over plain * HTTP, so it cannot be planted (cookie tossing). */ cookieName?: string; /** Request header carrying the token. Default `'x-csrf-token'`. */ headerName?: string; /** Form/JSON body field carrying the token when no header is present. Default `'_csrf'`. */ fieldName?: string; /** * Cookie attributes. Defaults to `{ sameSite: 'lax', path: '/', secure: true }`. * The cookie is readable by client JS by default (plain double-submit; read * it under {@link CsrfOptions.cookieName}, `__Host-bq.csrf` by default); set * `httpOnly: true` only when you deliver the token out-of-band (signed mode). * `secure` defaults to `true` (in signed mode the token embeds the raw * secret); set `secure: false` for local HTTP dev. */ cookie?: ServerCookieOptions; /** HTTP methods that skip verification. Default `['GET', 'HEAD', 'OPTIONS']`. */ ignoreMethods?: string[]; /** Custom token extractor, tried before the header and field lookups. */ getToken?: (ctx: ServerContext) => string | null | undefined | Promise; /** * In signed mode (`secret` set), keep the CSRF secret in `ctx.session` * instead of a cookie whenever the `session()` middleware ran first. This * binds tokens to the session, which the cookie cannot do. Requests that * arrive without a stored session use the signed cookie instead, so * anonymous traffic never creates sessions; a session created while * handling such a request adopts the cookie secret. * `$regenerate()` (e.g. on login) invalidates the secret; pages rendered * afterwards receive a fresh token. * Default `true`; set `false` to keep the cookie-based signed double-submit. */ bindToSession?: boolean; } /** * Read the CSRF token issued for the current request, suitable for embedding in * a form field, `` tag, or JSON payload. Returns `null` when the * {@link csrf} middleware has not run for this request. * * @example * ```ts * app.get('/form', (ctx) => * ctx.html(``) * ); * ``` */ export declare const csrfToken: (ctx: ServerContext) => string | null; /** * Middleware that enforces CSRF protection via the double-submit-cookie pattern. * * Safe requests (GET/HEAD/OPTIONS by default) mint a per-client secret cookie * and expose the matching token through {@link csrfToken}. State-changing * requests must echo that token back in the `x-csrf-token` header or a `_csrf` * body field, or they are rejected with `403`. * * @example * ```ts * import { createServer, csrf } from '@bquery/bquery/server'; * * const app = createServer(); * app.use(csrf({ secret: process.env.SECRET! })); * ``` */ export declare const csrf: (options?: CsrfOptions) => ServerMiddleware; //# sourceMappingURL=csrf.d.ts.map