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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user