Files

96 lines
2.6 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { getExpectedToken, isAuthenticated } from "@/lib/admin-auth";
import {
getClientIp,
constantTimeCompare,
checkAuthRateLimit,
recordAuthFailure,
recordAuthSuccess,
} from "@/lib/security";
export async function POST(req: NextRequest) {
const ip = getClientIp(req);
// 1. Check brute-force lockout
const authLimit = checkAuthRateLimit(ip, 5, 15);
if (!authLimit.allowed) {
const waitMins = Math.ceil(authLimit.lockoutSeconds / 60);
console.warn(`[Portal Auth Security] IP ${ip} blocked due to too many failed login attempts.`);
return NextResponse.json(
{
success: false,
error: `Too many failed login attempts. IP temporarily blocked. Try again in ${waitMins} minute${waitMins > 1 ? "s" : ""}.`,
},
{ status: 429 }
);
}
try {
const body = await req.json().catch(() => ({}));
const password = body.password;
const expectedPassword = process.env.ADMIN_PASSWORD || "admin123";
// 2. Validate password using timing-safe comparison
if (!password || typeof password !== "string" || !constantTimeCompare(password, expectedPassword)) {
recordAuthFailure(ip, 5, 15);
const remaining = authLimit.remainingAttempts - 1;
return NextResponse.json(
{
success: false,
error: "Incorrect password",
remainingAttempts: Math.max(0, remaining),
},
{ status: 401 }
);
}
// 3. Clear failed attempts on successful login
recordAuthSuccess(ip);
const token = getExpectedToken();
const response = NextResponse.json({
success: true,
token,
message: "Authentication successful",
});
response.cookies.set({
name: "booth_portal_token",
value: token,
httpOnly: true,
path: "/",
maxAge: 60 * 60 * 24 * 7, // 7 days
sameSite: "lax",
});
return response;
} catch (error: any) {
console.error("[Portal Auth] Login error:", error);
return NextResponse.json(
{ success: false, error: "Internal authentication error" },
{ status: 500 }
);
}
}
export async function GET(req: NextRequest) {
const authed = isAuthenticated(req);
if (authed) {
return NextResponse.json({ authenticated: true });
}
return NextResponse.json({ authenticated: false }, { status: 401 });
}
export async function DELETE() {
const response = NextResponse.json({ success: true, message: "Logged out" });
response.cookies.set({
name: "booth_portal_token",
value: "",
httpOnly: true,
path: "/",
maxAge: 0,
sameSite: "lax",
});
return response;
}