63 lines
1.6 KiB
TypeScript
63 lines
1.6 KiB
TypeScript
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 } });
|
||
|
|
}
|