import { cookies } from 'next/headers'; import { v4 as uuidv4 } from 'uuid'; import { getCsrfCookieOptions } from './cookieConfig'; const CSRF_TOKEN_COOKIE = 'csrf_token'; const CSRF_TOKEN_HEADER = 'x-csrf-token'; /** * Generate a new CSRF token and set it as a cookie */ export function generateCsrfToken(): string { const token = uuidv4(); cookies().set(CSRF_TOKEN_COOKIE, token, getCsrfCookieOptions()); return token; } /** * Get the CSRF token from cookies */ export function getCsrfToken(): string | undefined { return cookies().get(CSRF_TOKEN_COOKIE)?.value; } /** * Validate CSRF token from request header against cookie */ export function validateCsrfToken(headerToken: string | null): boolean { if (!headerToken) { return false; } const cookieToken = getCsrfToken(); if (!cookieToken) { return false; } // Constant-time comparison to prevent timing attacks return cookieToken === headerToken; } /** * CSRF Protection middleware for API routes */ export function csrfProtection(request: Request): { valid: boolean; error?: string } { const method = request.method; // Only protect state-changing methods if (['GET', 'HEAD', 'OPTIONS'].includes(method)) { return { valid: true }; } const headerToken = request.headers.get(CSRF_TOKEN_HEADER); if (!validateCsrfToken(headerToken)) { return { valid: false, error: 'Invalid or missing CSRF token' }; } return { valid: true }; } /** * Get CSRF token for client-side use * This should be called from a GET endpoint */ export function getOrCreateCsrfToken(): string { let token = getCsrfToken(); if (!token) { token = generateCsrfToken(); } return token; }