auto-claude: subtask-1-1 - Create server-side CSRF token generation and valid
This commit is contained in:
@@ -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<string> {
|
||||
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<void> {
|
||||
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<boolean> {
|
||||
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<void> {
|
||||
const cookieStore = await cookies();
|
||||
|
||||
try {
|
||||
cookieStore.delete(CSRF_TOKEN_COOKIE_NAME);
|
||||
} catch {
|
||||
// Handle server component context
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user