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"]); function generateCsrfToken(): string { const array = new Uint8Array(32); crypto.getRandomValues(array); let binary = ""; for (const b of array) binary += String.fromCharCode(b); return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); } export async function proxy(request: NextRequest) { const { pathname } = request.nextUrl; // Allow login page and login API if (pathname === "/admin/login" || pathname === "/api/auth/login") { return NextResponse.next(); } // Protect /admin/* and /api/admin/* const token = request.cookies.get(COOKIE_NAME)?.value; const valid = token ? await verifyToken(token) : false; if (!valid) { if (pathname.startsWith("/api/")) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } return NextResponse.redirect(new URL("/admin/login", request.url)); } // Auto-issue CSRF cookie if missing (e.g. session from before CSRF was added) const hasCsrf = request.cookies.has(CSRF_COOKIE_NAME); if (!hasCsrf) { const csrfToken = generateCsrfToken(); const response = NextResponse.next(); response.cookies.set(CSRF_COOKIE_NAME, csrfToken, { httpOnly: false, secure: process.env.NODE_ENV === "production", sameSite: "strict", path: "/", maxAge: 60 * 60 * 24, }); return response; } // 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(); } export const config = { matcher: ["/admin/:path*", "/api/admin/:path*"], };