import { NextRequest } from "next/server" import { comparePassword, getUserByEmail, getUserByUsername, mapDbUserToSessionUser, recordLoginAttempt, incrementFailedAttempts, resetFailedAttempts, isAccountLocked, createSession, setSessionContext, SESSION_COOKIE, } from "@/lib/auth" import { checkRateLimit } from "@/lib/rate-limit" function jsonResponse(data: unknown, status: number, headers?: Record) { return new Response(JSON.stringify(data), { status, headers: { "Content-Type": "application/json", ...headers }, }) } export async function POST(request: NextRequest) { try { const ipAddress = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || request.headers.get("x-real-ip") || "127.0.0.1" const rateCheck = checkRateLimit(`login:${ipAddress}`, 10, 60000) if (!rateCheck.allowed) { return jsonResponse( { error: "Too many login attempts. Try again in 60 seconds." }, 429, { "Retry-After": String(Math.ceil((rateCheck.resetAt - Date.now()) / 1000)), "X-RateLimit-Remaining": "0", }, ) } const { email, username, password } = await request.json() const credential = email || username if (!credential || !password) { return jsonResponse( { error: "Email/Username and password are required." }, 400 ) } if (credential.trim().length === 0 || password.trim().length === 0) { return jsonResponse( { error: "Credentials cannot be empty." }, 400 ) } const userAgent = request.headers.get("user-agent") || null // Try to find user by email first, then by username let dbUser = email || credential.includes("@") ? await getUserByEmail(credential) : null if (!dbUser) { dbUser = await getUserByUsername(credential) } if (!dbUser) { await recordLoginAttempt( null, credential, ipAddress, userAgent, false, "User not found" ) return jsonResponse( { error: "Invalid email/username or password." }, 401 ) } const lockStatus = await isAccountLocked(dbUser) if (lockStatus.locked) { await recordLoginAttempt( dbUser.id, credential, ipAddress, userAgent, false, lockStatus.reason ) return jsonResponse( { error: lockStatus.reason }, 423 ) } const valid = await comparePassword(password, dbUser.password_hash) if (!valid) { await incrementFailedAttempts(dbUser.id) await recordLoginAttempt( dbUser.id, credential, ipAddress, userAgent, false, "Invalid password" ) return jsonResponse( { error: "Invalid email/username or password." }, 401 ) } await resetFailedAttempts(dbUser.id) await recordLoginAttempt( dbUser.id, credential, ipAddress, userAgent, true ) const token = await createSession(dbUser.id, dbUser.role_name) await setSessionContext(dbUser.id, ipAddress) const user = mapDbUserToSessionUser(dbUser) const cookieStr = `${SESSION_COOKIE}=${token}; HttpOnly; SameSite=Strict; Path=/; Max-Age=86400${process.env.NODE_ENV === "production" ? "; Secure" : ""}` return new Response(JSON.stringify({ user }), { status: 200, headers: { "Content-Type": "application/json", "Set-Cookie": cookieStr, }, }) } catch (error) { console.error("Login error:", error) return new Response( JSON.stringify({ error: "Authentication service unavailable." }), { status: 503, headers: { "Content-Type": "application/json" } } ) } }