feat: add CSRF protection for admin API routes
Double-submit cookie pattern: login sets bh-csrf-token cookie, proxy.ts validates X-CSRF-Token header on POST/PUT/DELETE to /api/admin/*. New adminFetch() helper in src/lib/csrf.ts auto-includes the header. All admin pages migrated from fetch() to adminFetch(). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
14
src/proxy.ts
14
src/proxy.ts
@@ -1,6 +1,10 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { verifyToken, COOKIE_NAME } from "@/lib/auth-edge";
|
||||
|
||||
const CSRF_COOKIE_NAME = "bh-csrf-token";
|
||||
const CSRF_HEADER_NAME = "x-csrf-token";
|
||||
const STATE_CHANGING_METHODS = new Set(["POST", "PUT", "DELETE", "PATCH"]);
|
||||
|
||||
export async function proxy(request: NextRequest) {
|
||||
const { pathname } = request.nextUrl;
|
||||
|
||||
@@ -20,6 +24,16 @@ export async function proxy(request: NextRequest) {
|
||||
return NextResponse.redirect(new URL("/admin/login", request.url));
|
||||
}
|
||||
|
||||
// CSRF check on state-changing API requests
|
||||
if (pathname.startsWith("/api/admin/") && STATE_CHANGING_METHODS.has(request.method)) {
|
||||
const csrfCookie = request.cookies.get(CSRF_COOKIE_NAME)?.value;
|
||||
const csrfHeader = request.headers.get(CSRF_HEADER_NAME);
|
||||
|
||||
if (!csrfCookie || !csrfHeader || csrfCookie !== csrfHeader) {
|
||||
return NextResponse.json({ error: "CSRF token mismatch" }, { status: 403 });
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user