QR-master/src/lib/csrf.ts

80 lines
1.7 KiB
TypeScript

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;
}