export const dynamic = "force-dynamic"; import { eq } from "drizzle-orm"; import { z } from "zod"; import { getDb } from "@/lib/db/client"; import { users } from "@/lib/db/schema"; import { verifyPassword } from "@/lib/auth/password"; import { getSession } from "@/lib/auth/session"; import { isRateLimited, recordFailedAttempt, clearAttempts, } from "@/lib/auth/rate-limit"; import { logger } from "@/lib/logger"; const log = logger.child({ mod: "auth" }); const loginSchema = z.object({ username: z.string().min(1), password: z.string().min(1), }); export async function POST(request: Request) { const rateLimitKey = request.headers.get("x-forwarded-for") ?? "local"; if (isRateLimited(rateLimitKey)) { log.warn({ from: rateLimitKey }, "login rate-limited"); return Response.json( { error: "Too many attempts, try again later." }, { status: 429 }, ); } const body = await request.json().catch(() => null); const parsed = loginSchema.safeParse(body); if (!parsed.success) { return Response.json({ error: "Invalid request body" }, { status: 400 }); } const db = getDb(); const user = db .select() .from(users) .where(eq(users.username, parsed.data.username)) .get(); if ( !user || !(await verifyPassword(user.passwordHash, parsed.data.password)) ) { recordFailedAttempt(rateLimitKey); log.warn( { from: rateLimitKey, username: parsed.data.username }, "login failed: invalid credentials", ); return Response.json({ error: "Invalid credentials" }, { status: 401 }); } clearAttempts(rateLimitKey); db.update(users) .set({ lastLoginAt: new Date() }) .where(eq(users.id, user.id)) .run(); const session = await getSession(); session.userId = user.id; session.username = user.username; await session.save(); log.info({ from: rateLimitKey, username: user.username }, "login succeeded"); return Response.json({ user: { username: user.username } }); }