UNPKG

@csrf-armor/core

Version:

Framework-agnostic CSRF protection core functionality

127 lines (125 loc) 4.19 kB
import { OriginMismatchError } from "./errors.mjs"; import { parseSignedToken, timingSafeEqual, verifySignedToken } from "./crypto.mjs"; import { SAFE_METHODS } from "./constants.mjs"; //#region src/validation.ts function getHeaders(request) { if (request.headers instanceof Map) return request.headers; return new Map(Object.entries(request.headers)); } function getCookies(request) { if (request.cookies instanceof Map) return request.cookies; return new Map(Object.entries(request.cookies)); } async function validateSignedToken(request, config, getTokenFromRequest) { try { const token = await getTokenFromRequest(request, config); if (!token) return { isValid: false, reason: "No CSRF token provided" }; await parseSignedToken(token, config.secret); return { isValid: true }; } catch (error) { if (error instanceof Error) return { isValid: false, reason: error.message }; return { isValid: false, reason: "Unknown error" }; } } function validateOrigin(request, config) { const headers = getHeaders(request); const origin = headers.get("origin"); const referer = headers.get("referer"); if (!origin && !referer && !SAFE_METHODS.includes(request.method)) return { isValid: false, reason: "Missing origin and referer headers" }; const requestOrigin = origin ?? (referer ? new URL(referer).origin : null); if (!requestOrigin) return { isValid: false, reason: "No origin or referer header" }; if (config.allowedOrigins.includes(requestOrigin)) return { isValid: true }; return { isValid: false, reason: new OriginMismatchError(requestOrigin).message }; } async function validateDoubleSubmit(request, config, getTokenFromRequest) { const cookies = getCookies(request); const cookieName = config.cookie.name; const cookieToken = cookies.get(cookieName); const submittedToken = await getTokenFromRequest(request, config); if (!cookieToken) return { isValid: false, reason: "No CSRF cookie found" }; if (!submittedToken) return { isValid: false, reason: "No CSRF token submitted" }; if (!timingSafeEqual(cookieToken, submittedToken)) return { isValid: false, reason: "Token mismatch" }; return { isValid: true }; } async function validateSignedDoubleSubmit(request, config, getTokenFromRequest) { const cookies = getCookies(request); const cookieName = config.cookie.name; const unsignedCookieToken = cookies.get(cookieName); const signedCookieToken = cookies.get(`${cookieName}-server`); const submittedToken = await getTokenFromRequest(request, config); if (!unsignedCookieToken || !signedCookieToken) return { isValid: false, reason: "Missing CSRF cookies" }; if (!submittedToken) return { isValid: false, reason: "No CSRF token submitted" }; try { if (!timingSafeEqual(unsignedCookieToken, await verifySignedToken(signedCookieToken, config.secret))) return { isValid: false, reason: "Cookie integrity check failed" }; if (!timingSafeEqual(submittedToken, unsignedCookieToken)) return { isValid: false, reason: "Token mismatch" }; return { isValid: true }; } catch (error) { if (error instanceof Error) return { isValid: false, reason: error.message }; return { isValid: false, reason: "Token validation failed" }; } } async function validateRequest(request, config, getTokenFromRequest) { switch (config.strategy) { case "signed-token": return await validateSignedToken(request, config, getTokenFromRequest); case "origin-check": return validateOrigin(request, config); case "double-submit": return await validateDoubleSubmit(request, config, getTokenFromRequest); case "signed-double-submit": return await validateSignedDoubleSubmit(request, config, getTokenFromRequest); case "hybrid": { const originResult = validateOrigin(request, config); if (!originResult.isValid) return originResult; return await validateSignedToken(request, config, getTokenFromRequest); } default: return { isValid: false, reason: "Invalid strategy" }; } } //#endregion export { validateDoubleSubmit, validateOrigin, validateRequest, validateSignedDoubleSubmit, validateSignedToken }; //# sourceMappingURL=validation.mjs.map