MBO-Tech-IT-Webseite/app/api/admin/login/route.ts

100 lines
3.6 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import bcrypt from "bcryptjs";
import { createServiceClient } from "@/lib/supabase";
import { createSessionToken, verifySessionToken } from "@/lib/admin-auth";
import { logLoginAttempt, getFailedLoginCount, sendSecurityAlert } from "@/lib/audit-log";
import { checkRateLimit, resetRateLimit } from "@/lib/rate-limit";
import { revokeSessionToken } from "@/lib/token-blacklist";
export async function POST(req: NextRequest) {
const { email, password } = await req.json();
const ipAddr = req.headers.get("x-forwarded-for") || req.headers.get("x-real-ip") || "unknown";
const userAgent = req.headers.get("user-agent") || "unknown";
if (!email || !password) {
await logLoginAttempt("", ipAddr, false, userAgent, "missing_credentials");
return NextResponse.json({ error: "E-Mail und Passwort erforderlich" }, { status: 400 });
}
const rateLimitKey = `login:${email.toLowerCase()}:${ipAddr}`;
const { allowed, delayMs, locked } = checkRateLimit(rateLimitKey);
if (locked) {
await logLoginAttempt(email, ipAddr, false, userAgent, "rate_limit_locked");
const res = NextResponse.json({ error: "Zu viele Anmeldeversuche. Bitte später versuchen." }, { status: 429 });
res.headers.set("Retry-After", String(Math.ceil(delayMs / 1000)));
return res;
}
if (!allowed && delayMs > 0) {
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
const db = createServiceClient();
const { data: admin } = await db
.from("admin_users")
.select("id, email, name, password_hash, aktiv")
.eq("email", email.toLowerCase().trim())
.single();
if (!admin || !admin.aktiv) {
await logLoginAttempt(email, ipAddr, false, userAgent, "user_not_found_or_inactive");
const failedCount = await getFailedLoginCount(email, "email", 60);
if (failedCount >= 10) {
await sendSecurityAlert(
"⚠️ Viele fehlgeschlagene Login-Versuche",
`Email: ${email}\nVersuche in der letzten Stunde: ${failedCount}`
);
}
return NextResponse.json({ error: "Ungültige Zugangsdaten" }, { status: 401 });
}
const valid = await bcrypt.compare(password, admin.password_hash);
if (!valid) {
await logLoginAttempt(email, ipAddr, false, userAgent, "invalid_password");
const failedCount = await getFailedLoginCount(email, "email", 60);
if (failedCount >= 10) {
await sendSecurityAlert(
"⚠️ Viele fehlgeschlagene Login-Versuche (falsches Passwort)",
`Email: ${email}\nIP: ${ipAddr}\nVersuche in der letzten Stunde: ${failedCount}`
);
}
return NextResponse.json({ error: "Ungültige Zugangsdaten" }, { status: 401 });
}
await logLoginAttempt(email, ipAddr, true, userAgent);
resetRateLimit(rateLimitKey);
const token = await createSessionToken({ id: admin.id, email: admin.email, name: admin.name ?? "" });
const res = NextResponse.json({ success: true });
res.cookies.set("admin_session", token, {
httpOnly: true,
sameSite: "lax",
maxAge: 60 * 60 * 2,
path: "/",
secure: process.env.NODE_ENV === "production",
});
return res;
}
export async function DELETE(req: NextRequest) {
const token = req.cookies.get("admin_session")?.value;
if (token) {
try {
const session = await verifySessionToken(token);
if (session) {
const [, sig] = token.split(".");
await revokeSessionToken(sig, session.id, "logout");
}
} catch {
// Logout auch bei Fehler durchführen
}
}
const res = NextResponse.json({ success: true });
res.cookies.delete("admin_session");
return res;
}