import { NextResponse } from "next/server" const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD const RATE_LIMIT_WINDOW_MS = 15 * 60 * 1000 // 15 minutes const RATE_LIMIT_MAX_ATTEMPTS = 5 const attemptCounts = new Map() function getClientIp(req: Request): string { const forwarded = req.headers.get("x-forwarded-for") const realIp = req.headers.get("x-real-ip") if (forwarded) return forwarded.split(",")[0]?.trim() ?? "unknown" if (realIp) return realIp return "unknown" } function isRateLimited(ip: string): boolean { const now = Date.now() const entry = attemptCounts.get(ip) if (!entry) return false if (now >= entry.resetAt) { attemptCounts.delete(ip) return false } return entry.count >= RATE_LIMIT_MAX_ATTEMPTS } function recordFailedAttempt(ip: string): void { const now = Date.now() const entry = attemptCounts.get(ip) if (!entry) { attemptCounts.set(ip, { count: 1, resetAt: now + RATE_LIMIT_WINDOW_MS }) return } if (now >= entry.resetAt) { attemptCounts.set(ip, { count: 1, resetAt: now + RATE_LIMIT_WINDOW_MS }) return } entry.count += 1 } /** * GET: Check if admin password gate is enabled (so the client knows whether to show the modal). * POST: Verify admin password and allow access. Rate limited by IP. */ export async function GET() { return NextResponse.json({ adminPasswordRequired: !!ADMIN_PASSWORD && ADMIN_PASSWORD.length > 0, }) } export async function POST(req: Request) { if (!ADMIN_PASSWORD || ADMIN_PASSWORD.length === 0) { return NextResponse.json({ ok: true, notRequired: true }) } const ip = getClientIp(req) if (isRateLimited(ip)) { return NextResponse.json( { error: "Too many attempts. Please try again in 15 minutes." }, { status: 429 } ) } try { const body = await req.json() const password = typeof body?.password === "string" ? body.password : "" if (password === ADMIN_PASSWORD) { attemptCounts.delete(ip) return NextResponse.json({ ok: true }) } recordFailedAttempt(ip) return NextResponse.json( { error: "Invalid admin password" }, { status: 401 } ) } catch { return NextResponse.json( { error: "Invalid request" }, { status: 400 } ) } }