Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/lib/apiClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Utility tool to handle CSRF tokens and safe data-mutating network configurations
*/

// Helper to securely extract cookie string tokens browser-side
function getCookieValue(cookieName: string): string | null {
if (typeof document === 'undefined') return null;
const match = document.cookie.match(new RegExp('(^| )' + cookieName + '=([^;]+)'));
return match ? decodeURIComponent(match[2]) : null;
}

// Helper to generate a random cryptographic token string if missing
function generateSecureToken(): string {
return Math.random().toString(36).substring(2) + Math.random().toString(36).substring(2);
}

/**
* Custom Fetch Wrapper that automatically injects CSRF headers for mutating requests
*/
export async function secureFetch(url: string, options: RequestInit = {}): Promise<Response> {
const method = options.method?.toUpperCase() || 'GET';
const mutatingMethods = ['POST', 'PUT', 'DELETE', 'PATCH'];

// Initialize or maintain client-side tracking tokens if missing
if (typeof document !== 'undefined' && !getCookieValue('csrfToken')) {
const freshToken = generateSecureToken();
document.cookie = `csrfToken=${freshToken}; path=/; max-age=3600; SameSite=Strict; Secure`;
}

const token = getCookieValue('csrfToken');
const secureHeaders = new Headers(options.headers || {});

// Automatically attach the required token to the matching header fields
if (mutatingMethods.includes(method) && token) {
secureHeaders.set('x-csrf-token', token);
}

return fetch(url, {
...options,
headers: secureHeaders,
});
}
27 changes: 27 additions & 0 deletions src/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Crucial Security Patch: Implement CSRF protection for state-mutating requests (#359)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export async function middleware(request: NextRequest) {
const mutatingMethods = ['POST', 'PUT', 'DELETE', 'PATCH'];

// Check if the request is trying to mutate data
if (mutatingMethods.includes(request.method)) {
const csrfTokenHeader = request.headers.get('x-csrf-token');
const csrfCookieToken = request.cookies.get('csrfToken')?.value;

// Block request if tokens are missing or do not match
if (!csrfTokenHeader || !csrfCookieToken || csrfTokenHeader !== csrfCookieToken) {
return new NextResponse(
JSON.stringify({ success: false, message: 'CSRF token validation failed.' }),
{ status: 403, headers: { 'content-type': 'application/json' } }
);
}
}
return NextResponse.next();
}

// Config to apply this middleware across all API routes
export const config = {
matcher: '/api/:path*',
};