import { NextRequest, NextResponse } from "next/server"; import { prisma } from "@/lib/db"; import { createSession } from "@/lib/auth"; import { checkRateLimit } from "@/lib/rateLimiter"; // Progressive lockout thresholds from Section 4 function getLockoutDuration(failedAttempts: number): number { if (failedAttempts >= 20) return 24 * 60 * 60 * 1000; // 24 hours if (failedAttempts >= 15) return 30 * 60 * 1000; // 30 minutes if (failedAttempts >= 10) return 5 * 60 * 1000; // 5 minutes if (failedAttempts >= 5) return 1 * 60 * 1000; // 1 minute return 0; } export async function POST(request: NextRequest) { // Rate limit by IP const forwarded = request.headers.get("x-forwarded-for"); const ip = forwarded?.split(",")[0]?.trim() ?? "unknown"; const rateCheck = checkRateLimit(ip); if (!rateCheck.allowed) { return NextResponse.json( { error: "Too many requests. Try again shortly." }, { status: 429 } ); } const body = await request.json().catch(() => null); if (!body?.password || typeof body.password !== "string") { return NextResponse.json( { error: "Password is required" }, { status: 400 } ); } // Check lockout state let security = await prisma.authSecurity.findUnique({ where: { id: 1 } }); if (!security) { security = await prisma.authSecurity.create({ data: { id: 1 } }); } if (security.lockedUntil && security.lockedUntil > new Date()) { const remainingMs = security.lockedUntil.getTime() - Date.now(); const remainingMin = Math.ceil(remainingMs / 60000); return NextResponse.json( { error: `Account locked. Try again in ${remainingMin} minute${remainingMin !== 1 ? "s" : ""}.`, }, { status: 423 } ); } // Verify password const setting = await prisma.setting.findUnique({ where: { key: "admin_password_hash" }, }); let isValid = false; if (!setting) { // Initial setup: hash and save the new password const argon2 = await import("argon2"); const newHash = await argon2.hash(body.password); await prisma.setting.create({ data: { key: "admin_password_hash", value: newHash, }, }); isValid = true; } else { // Verify existing password const passwordHash = setting.value; try { const argon2 = await import("argon2"); isValid = await argon2.verify(passwordHash, body.password); } catch { isValid = body.password === passwordHash; } } if (!isValid) { const newFailedAttempts = security.failedAttempts + 1; const lockoutMs = getLockoutDuration(newFailedAttempts); const lockedUntil = lockoutMs > 0 ? new Date(Date.now() + lockoutMs) : null; await prisma.authSecurity.update({ where: { id: 1 }, data: { failedAttempts: newFailedAttempts, lockedUntil, lastAttemptAt: new Date(), }, }); return NextResponse.json( { error: "Invalid password" }, { status: 401 } ); } // Success — reset lockout, create session await prisma.authSecurity.update({ where: { id: 1 }, data: { failedAttempts: 0, lockedUntil: null, lastAttemptAt: new Date(), }, }); await createSession(); return NextResponse.json({ success: true }); }