UNPKG

@csrf-armor/nextjs

Version:

CSRF protection middleware for Next.js applications

97 lines (96 loc) 4.04 kB
//#region src/adapter.ts var NextjsAdapter = class { constructor() { this.parsedBodyCache = /* @__PURE__ */ new WeakMap(); this.getTokenFromRequest = this.getTokenFromRequest.bind(this); } extractRequest(req) { const cookies = /* @__PURE__ */ new Map(); for (const { name, value } of req.cookies.getAll()) cookies.set(name, value); return { method: req.method, url: req.url, headers: req.headers, cookies, body: req }; } applyResponse(res, csrfResponse) { if (csrfResponse.headers instanceof Map) for (const [key, value] of csrfResponse.headers) res.headers.set(key, value); else for (const [key, value] of Object.entries(csrfResponse.headers)) res.headers.set(key, String(value)); if (csrfResponse.cookies instanceof Map) for (const [name, cookieData] of csrfResponse.cookies) { const { value, options } = cookieData; res.cookies.set(name, value, this.adaptCookieOptions(options)); } else for (const [name, cookieData] of Object.entries(csrfResponse.cookies)) { const { value, options } = cookieData; res.cookies.set(name, value, this.adaptCookieOptions(options)); } return res; } async getTokenFromRequest(request, config) { const headers = request.headers; const nextRequest = request.body; const headerValue = headers.get(config.token.headerName.toLowerCase()); if (headerValue) return headerValue; const clientCookieValue = nextRequest.cookies?.get(config.cookie.name.toLowerCase())?.value; if (clientCookieValue) return clientCookieValue; let parsedBody; if (this.parsedBodyCache.has(nextRequest)) parsedBody = this.parsedBodyCache.get(nextRequest); else { const contentType = headers.get("content-type") ?? "text/plain"; try { if (nextRequest.bodyUsed) { console.warn("Request body was already consumed externally. CSRF token might not be extractable from body."); parsedBody = null; } else if (contentType.startsWith("application/x-www-form-urlencoded") || contentType.startsWith("multipart/form-data")) parsedBody = await nextRequest.formData(); else if (contentType === "application/json" || contentType === "application/ld+json") parsedBody = await nextRequest.json(); else if (contentType.startsWith("text/plain")) parsedBody = await nextRequest.text(); else parsedBody = null; this.parsedBodyCache.set(nextRequest, parsedBody); } catch (error) { console.warn("Failed to parse request body for CSRF token extraction", error); this.parsedBodyCache.set(nextRequest, null); parsedBody = null; } } if (parsedBody instanceof FormData) { for (const [key, value] of parsedBody.entries()) if (key === config.token.fieldName) return value.toString(); } else if (parsedBody && typeof parsedBody === "object") { const jsonVal = parsedBody[config.token.fieldName]; if (typeof jsonVal === "string") return jsonVal; if (Array.isArray(parsedBody) && parsedBody.length > 0) return this.extractTokenFromServerActionArgs(parsedBody, config); } else if (typeof parsedBody === "string") try { const tokenValue = new URLSearchParams(parsedBody).get(config.token.fieldName); if (tokenValue) return tokenValue; } catch (error) { console.warn("Failed to parse string body as URL-encoded form data", error); } } extractTokenFromServerActionArgs(args, config) { const firstArg = args[0]; if (typeof firstArg === "string") return firstArg; if (firstArg && typeof firstArg === "object") { const token = firstArg[config.token.fieldName]; if (typeof token === "string") return token; } for (const arg of args) if (arg && typeof arg === "object") { const token = arg[config.token.fieldName]; if (typeof token === "string") return token; } } adaptCookieOptions(options) { if (!options) return {}; return { secure: options.secure, httpOnly: options.httpOnly, sameSite: options.sameSite, path: options.path, domain: options.domain, maxAge: options.maxAge }; } }; //#endregion export { NextjsAdapter }; //# sourceMappingURL=adapter.js.map