Files
triggershell/src/app/api/auth/login/route.ts
T

63 lines
1.6 KiB
TypeScript
Raw Normal View History

2026-08-15 18:37:30 +02:00
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";
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)) {
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);
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();
return Response.json({ user: { username: user.username } });
}