From b1e89b64c595cedc8b991ca90e19e28c492d9b87 Mon Sep 17 00:00:00 2001 From: Damjan Savic Date: Sun, 25 Jan 2026 06:31:45 +0100 Subject: [PATCH] auto-claude: subtask-1-1 - Create server-side CSRF token generation and valid --- src/lib/csrf/server.ts | 94 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 src/lib/csrf/server.ts diff --git a/src/lib/csrf/server.ts b/src/lib/csrf/server.ts new file mode 100644 index 0000000..6abb546 --- /dev/null +++ b/src/lib/csrf/server.ts @@ -0,0 +1,94 @@ +import { cookies } from 'next/headers'; +import { randomBytes } from 'crypto'; + +const CSRF_TOKEN_COOKIE_NAME = 'csrf_token'; +const CSRF_TOKEN_LENGTH = 32; + +/** + * Generate a cryptographically secure CSRF token + */ +export function generateCsrfToken(): string { + return randomBytes(CSRF_TOKEN_LENGTH).toString('base64url'); +} + +/** + * Get the current CSRF token from cookies or generate a new one + */ +export async function getCsrfToken(): Promise { + const cookieStore = await cookies(); + const existingToken = cookieStore.get(CSRF_TOKEN_COOKIE_NAME); + + if (existingToken?.value) { + return existingToken.value; + } + + const newToken = generateCsrfToken(); + await setCsrfToken(newToken); + return newToken; +} + +/** + * Set the CSRF token in an httpOnly cookie + */ +export async function setCsrfToken(token: string): Promise { + const cookieStore = await cookies(); + + try { + cookieStore.set(CSRF_TOKEN_COOKIE_NAME, token, { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'strict', + maxAge: 60 * 60 * 24, // 24 hours + path: '/', + }); + } catch { + // Handle server component context where cookies can't be set + } +} + +/** + * Validate a CSRF token against the stored token in cookies + */ +export async function validateCsrfToken(token: string): Promise { + const cookieStore = await cookies(); + const storedToken = cookieStore.get(CSRF_TOKEN_COOKIE_NAME); + + if (!storedToken?.value || !token) { + return false; + } + + // Constant-time comparison to prevent timing attacks + return timingSafeEqual( + Buffer.from(storedToken.value), + Buffer.from(token) + ); +} + +/** + * Timing-safe string comparison to prevent timing attacks + */ +function timingSafeEqual(a: Buffer, b: Buffer): boolean { + if (a.length !== b.length) { + return false; + } + + let result = 0; + for (let i = 0; i < a.length; i++) { + result |= a[i] ^ b[i]; + } + + return result === 0; +} + +/** + * Delete the CSRF token cookie + */ +export async function deleteCsrfToken(): Promise { + const cookieStore = await cookies(); + + try { + cookieStore.delete(CSRF_TOKEN_COOKIE_NAME); + } catch { + // Handle server component context + } +}