mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 12:32:10 +01:00
feat: implement comprehensive CSRF protection
This commit is contained in:
124
middleware/csrfProtection.ts
Normal file
124
middleware/csrfProtection.ts
Normal file
@ -0,0 +1,124 @@
|
||||
/**
|
||||
* CSRF Protection Middleware
|
||||
*
|
||||
* This middleware protects against Cross-Site Request Forgery attacks by:
|
||||
* 1. Validating CSRF tokens on state-changing operations
|
||||
* 2. Generating new tokens for safe requests
|
||||
* 3. Blocking unauthorized requests
|
||||
*/
|
||||
|
||||
import type { NextRequest } from "next/server";
|
||||
import { NextResponse } from "next/server";
|
||||
import { CSRFProtection, CSRF_CONFIG } from "../lib/csrf";
|
||||
|
||||
/**
|
||||
* Routes that require CSRF protection
|
||||
*/
|
||||
const PROTECTED_PATHS = [
|
||||
// Authentication endpoints
|
||||
"/api/auth/signin",
|
||||
"/api/auth/signout",
|
||||
"/api/register",
|
||||
"/api/forgot-password",
|
||||
"/api/reset-password",
|
||||
|
||||
// Dashboard API endpoints
|
||||
"/api/dashboard",
|
||||
|
||||
// Platform admin endpoints
|
||||
"/api/platform",
|
||||
|
||||
// tRPC endpoints (all state-changing operations)
|
||||
"/api/trpc",
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* HTTP methods that require CSRF protection
|
||||
*/
|
||||
const PROTECTED_METHODS = ["POST", "PUT", "DELETE", "PATCH"] as const;
|
||||
|
||||
/**
|
||||
* Check if path requires CSRF protection
|
||||
*/
|
||||
function requiresCSRFProtection(pathname: string, method: string): boolean {
|
||||
// Only protect state-changing methods
|
||||
if (!PROTECTED_METHODS.includes(method as any)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if path starts with any protected path
|
||||
return PROTECTED_PATHS.some((path) => pathname.startsWith(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* CSRF protection middleware
|
||||
*/
|
||||
export async function csrfProtectionMiddleware(
|
||||
request: NextRequest
|
||||
): Promise<NextResponse> {
|
||||
const { pathname } = request.nextUrl;
|
||||
const method = request.method;
|
||||
|
||||
// Skip CSRF protection for safe methods and unprotected paths
|
||||
if (!requiresCSRFProtection(pathname, method)) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
// Validate CSRF token for protected requests
|
||||
const validation = await CSRFProtection.validateRequest(request);
|
||||
|
||||
if (!validation.valid) {
|
||||
console.warn(`CSRF validation failed for ${method} ${pathname}:`, validation.error);
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "Invalid or missing CSRF token",
|
||||
code: "CSRF_TOKEN_INVALID",
|
||||
},
|
||||
{
|
||||
status: 403,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate CSRF token endpoint response
|
||||
*/
|
||||
export function generateCSRFTokenResponse(): NextResponse {
|
||||
const { token, cookie } = CSRFProtection.generateTokenResponse();
|
||||
|
||||
const response = NextResponse.json({
|
||||
success: true,
|
||||
token,
|
||||
});
|
||||
|
||||
// Set the CSRF token cookie
|
||||
response.cookies.set(
|
||||
cookie.name,
|
||||
cookie.value,
|
||||
cookie.options
|
||||
);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware for serving CSRF tokens to clients
|
||||
*/
|
||||
export function csrfTokenMiddleware(request: NextRequest): NextResponse | null {
|
||||
const { pathname } = request.nextUrl;
|
||||
|
||||
// Handle CSRF token endpoint
|
||||
if (pathname === "/api/csrf-token" && request.method === "GET") {
|
||||
return generateCSRFTokenResponse();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user