/** * Extract the real client IP from a request-like object, honouring a * trust-proxy allowlist. Node behind a load balancer sees the LB's IP * in `req.socket.remoteAddress`; the real client sits inside * `X-Forwarded-For` — but only reachable safely by walking the header * **right-to-left** past the trusted proxy hops, because a client can * forge left-most entries and every conforming proxy *appends* the * real address. * * ### `trustProxy` semantics * * | Value | Meaning | * | ------------------ | ------------------------------------------------------------------------------------------- | * | `false` (default) | Ignore XFF entirely; return `socket.remoteAddress`. Only correct when Node is edge-facing. | * | `true` | Trust every hop. Returns the left-most XFF entry — **spoofable unless the first proxy strips inbound XFF**. | * | `string[]` | Right-to-left walk skipping entries whose value is in the set; returns the first untrusted hop. | * * ### `proxyCount` * * Alternative to `trustProxy: string[]` when the proxy chain depth is * known but the addresses aren't stable (e.g. Cloudflare + a k8s * ingress). Skips **N** rightmost XFF entries and returns the * `(N + 1)`-th from the right — i.e. the last hop before the trusted * chain begins. Wins over `trustProxy` when both are set. * * @param {{ * headers?: Record, * socket?: { remoteAddress?: string }, * ip?: string, * }} req * @param {{ * trustProxy?: boolean | string[], * proxyCount?: number, * headers?: string[], * }} [options] * @returns {string | undefined} */ export function getClientIp(req: { headers?: Record; socket?: { remoteAddress?: string; }; ip?: string; }, options?: { trustProxy?: boolean | string[]; proxyCount?: number; headers?: string[]; }): string | undefined; /** * Parse an `Authorization: Bearer ` header. Case-insensitive on the * scheme (RFC 7235 §2.1). Returns the token, or `null` when the header is * missing / malformed / uses a different scheme. * * @param {string | undefined | null} headerValue * @returns {string | null} */ export function bearer(headerValue: string | undefined | null): string | null; /** * Defensive Origin / Referer check for state-changing requests. GET / HEAD * are usually excluded; POST / PUT / DELETE / PATCH should carry a same- * origin `Origin` header (`Referer` as a fallback for older browsers). * * Complements CORS: CORS controls cross-origin READS, this catches CSRF- * like requests where a cookie is present but Origin doesn't match. * * @param {{ * method?: string, * headers?: Record, * }} req * @param {{ * allowedOrigins: Array, * safeMethods?: string[], * }} options * @returns {boolean} true if the request may proceed */ export function checkOrigin(req: { method?: string; headers?: Record; }, options: { allowedOrigins: Array; safeMethods?: string[]; }): boolean; /** * Verify a webhook payload against an HMAC signature (constant-time). * * `signatureHeader` may be the plain hex digest OR a scheme-prefixed * variant like `sha256=` (GitHub convention). Multiple * comma-separated candidates are accepted, and any candidate that * matches wins. * * For Stripe-style timestamped envelopes (`t=,v1=`) use * {@link webhookVerifyStripe} — those need a replay-tolerance window * this helper deliberately does not enforce. * * @param {string | Buffer} payload Raw request body — DO NOT stringify JSON first. * @param {string} signatureHeader Value from the incoming signature header. * @param {string | Buffer} secret Shared secret (32 bytes minimum recommended). * @param {{ algorithm?: 'sha256' | 'sha512' }} [options] * `algorithm` — HMAC hash. Restricted to `sha256` (default) and * `sha512`; anything else throws. Weaker hashes (`sha1`) are * deliberately not accepted even though `node:crypto` supports them. * @returns {boolean} */ export function webhookVerify(payload: string | Buffer, signatureHeader: string, secret: string | Buffer, options?: { algorithm?: "sha256" | "sha512"; }): boolean; /** * Verify a Stripe-style timestamped webhook envelope (constant-time * HMAC + replay-tolerance window). * * The header is a comma-separated list of `key=value` pairs: * * `t=1614265636,v1=,v0=` * * where `t` is a Unix timestamp in **seconds**, `v1` is the HMAC-SHA-256 * of `${t}.${payload}` (Stripe's current scheme, and what this helper * checks), and `v0` is the legacy scheme (this helper ignores it). * Multiple `v1=` entries are accepted — Stripe rotates secrets by * signing with several at once. * * The signature check is timing-safe; a stale timestamp fails without * running the HMAC compare (freshness gates freshness, not signature * validity). * * @param {string | Buffer} payload Raw request body — DO NOT stringify JSON first. * @param {string} signatureHeader Stripe's `Stripe-Signature` header value. * @param {string | Buffer | Array} secret * Endpoint signing secret (or array of secrets for rotation). Any * candidate that matches wins. * @param {{ tolerance?: number, now?: number }} [options] * `tolerance` — allowed clock skew in **seconds**. Default 300s * (Stripe's own recommendation). Both past and future skew are * checked so a clock drift on either side rejects the same way. * `now` — override for tests. Unix seconds. * @returns {boolean} */ export function webhookVerifyStripe(payload: string | Buffer, signatureHeader: string, secret: string | Buffer | Array, options?: { tolerance?: number; now?: number; }): boolean;