@csrf-armor/express
Version:
Express.js adapter for CSRF Armor - Advanced CSRF protection for Express.js applications
158 lines (157 loc) • 5.58 kB
JavaScript
//#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 headerValue = (request.headers instanceof Map ? request.headers : new Map(Object.entries(request.headers))).get(config.token.headerName.toLowerCase());
if (headerValue) return headerValue;
const cookieValue = (request.cookies instanceof Map ? request.cookies : new Map(Object.entries(request.cookies || {}).map(([key, value]) => [key.toLowerCase(), String(value)]))).get(config.cookie.name.toLowerCase());
if (cookieValue) return cookieValue;
if (request.url) try {
const queryValue = new URL(request.url, "http://localhost").searchParams.get(config.token.fieldName);
if (queryValue) return queryValue;
} catch {}
if (request.body && typeof request.body === "object") {
const formValue = request.body[config.token.fieldName];
if (typeof formValue === "string") return formValue;
}
}
};
//#endregion
export { ExpressAdapter };
//# sourceMappingURL=adapter.mjs.map