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:
2026-03-17 17:53:02 +03:00
parent 3ac6a4d840
commit 6cbdba2197
12 changed files with 161 additions and 53 deletions

View File

@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from "next/server";
import { verifyPassword, signToken, COOKIE_NAME } from "@/lib/auth";
import { verifyPassword, signToken, generateCsrfToken, COOKIE_NAME, CSRF_COOKIE_NAME } from "@/lib/auth";
export async function POST(request: NextRequest) {
const body = await request.json() as { password?: string };
@@ -9,6 +9,7 @@ export async function POST(request: NextRequest) {
}
const token = signToken();
const csrfToken = generateCsrfToken();
const response = NextResponse.json({ ok: true });
response.cookies.set(COOKIE_NAME, token, {
@@ -16,7 +17,15 @@ export async function POST(request: NextRequest) {
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
path: "/",
maxAge: 60 * 60 * 24, // 24 hours
maxAge: 60 * 60 * 24,
});
response.cookies.set(CSRF_COOKIE_NAME, csrfToken, {
httpOnly: false, // JS must read this to send as header
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
path: "/",
maxAge: 60 * 60 * 24,
});
return response;