UNPKG

@csrf-armor/express

Version:

Express.js adapter for CSRF Armor - Advanced CSRF protection for Express.js applications

163 lines (162 loc) 5.68 kB
//#region src/adapter.ts /** * Express.js adapter for CSRF protection. * * This adapter implements the CsrfAdapter interface to provide CSRF protection * for Express.js applications. It handles request/response transformation, * token extraction from various sources, and cookie/header management. * * @example * ```typescript * import { CsrfProtection } from '@csrf-armor/core'; * import { ExpressAdapter } from '@csrf-armor/express'; * * const csrf = new CsrfProtection(new ExpressAdapter(), { * secret: 'your-secret-key', * strategy: 'signed-double-submit' * }); * ``` */ var ExpressAdapter = class { /** * Extracts CSRF-relevant data from an Express.js request. * * Converts an Express Request object into a standardized CsrfRequest format * that can be processed by the core CSRF protection logic. Handles header * normalization, cookie extraction, and body access. * * @param req - Express.js request object * @returns Normalized CSRF request object * * @example * ```typescript * const adapter = new ExpressAdapter(); * const csrfRequest = adapter.extractRequest(req); * // csrfRequest contains normalized headers, cookies, and body * ``` */ extractRequest(req) { const headers = /* @__PURE__ */ new Map(); if (req.headers) Object.entries(req.headers).forEach(([key, value]) => { if (Array.isArray(value)) headers.set(key.toLowerCase(), value.join(", ")); else if (value !== void 0) headers.set(key.toLowerCase(), value); }); return { method: req.method, url: req.url, headers, cookies: new Map(Object.entries(req.cookies ?? {}).map(([key, value]) => [key.toLowerCase(), String(value)])), body: req.body }; } /** * Sets a cookie on the Express.js response with CSRF protection options. * * Converts CSRF armor cookie options to Express.js cookie format, * including proper maxAge conversion (seconds to milliseconds). * * @param res - Express.js response object * @param name - Cookie name * @param value - Cookie value * @param options - CSRF cookie options * * @private */ setCookie(res, name, value, options) { res.cookie(name, value, { secure: options?.secure, httpOnly: options?.httpOnly, sameSite: options?.sameSite, path: options?.path, domain: options?.domain, maxAge: options?.maxAge ? options.maxAge * 1e3 : void 0 }); } /** * Sets HTTP headers on the Express.js response. * * Handles both Map and object-based header formats from the CSRF response. * * @param res - Express.js response object * @param headers - Headers to set (Map or object format) * * @private */ setHeaders(res, headers) { const entries = headers instanceof Map ? headers : Object.entries(headers || {}); for (const [key, value] of entries) res.setHeader(key, value); } /** * Applies CSRF protection data to an Express.js response. * * Sets headers and cookies on the Express response based on the CSRF * protection results. This includes CSRF tokens, strategy hints, and * security cookies. * * @param res - Express.js response object to modify * @param csrfResponse - CSRF response data containing headers and cookies * @returns The modified Express.js response object * * @example * ```typescript * const adapter = new ExpressAdapter(); * const modifiedResponse = adapter.applyResponse(res, { * headers: new Map([['x-csrf-token', 'abc123']]), * cookies: new Map([['csrf-token', { value: 'def456', options: { httpOnly: true } }]]) * }); * ``` */ applyResponse(res, csrfResponse) { this.setHeaders(res, csrfResponse.headers); if (csrfResponse.cookies) { const entries = csrfResponse.cookies instanceof Map ? Array.from(csrfResponse.cookies.entries()) : Object.entries(csrfResponse.cookies); for (const [name, { value, options }] of entries) this.setCookie(res, name, value, options); } return res; } /** * Extracts CSRF token from various locations in an Express.js request. * * Attempts to find the CSRF token in the following order: * 1. HTTP headers (e.g., X-CSRF-Token) * 2. URL query parameters * 3. Request body (form data or JSON) * * This method handles relative URLs safely by providing a base URL for parsing. * * @param request - Normalized CSRF request object * @param config - CSRF configuration containing token field names * @returns The extracted token string, or undefined if not found * * @example * ```typescript * const adapter = new ExpressAdapter(); * const token = await adapter.getTokenFromRequest(csrfRequest, { * token: { headerName: 'X-CSRF-Token', fieldName: 'csrf_token' } * }); * // Returns token from header, query param, or body * ``` */ async getTokenFromRequest(request, config) { const headers = request.headers instanceof Map ? request.headers : new Map(Object.entries(request.headers)); const headerValue = headers.get(config.token.headerName.toLowerCase()); if (headerValue) return headerValue; const cookies = request.cookies instanceof Map ? request.cookies : new Map(Object.entries(request.cookies || {}).map(([key, value]) => [key.toLowerCase(), String(value)])); const cookieValue = cookies.get(config.cookie.name.toLowerCase()); if (cookieValue) return cookieValue; if (request.url) try { const url = new URL(request.url, "http://localhost"); const queryValue = url.searchParams.get(config.token.fieldName); if (queryValue) return queryValue; } catch {} if (request.body && typeof request.body === "object") { const body = request.body; const formValue = body[config.token.fieldName]; if (typeof formValue === "string") return formValue; } return void 0; } }; //#endregion export { ExpressAdapter }; //# sourceMappingURL=adapter.js.map