import { timingSafeEqual } from "node:crypto"; import { z } from "zod"; import { env } from "#server/env"; const bodySchema = z.object({ username: z.string(), password: z.string(), }); const attempts = new Map(); const MAX_ATTEMPTS = 10; const WINDOW_MS = 60_000; const isRateLimited = (ip: string): boolean => { const now = Date.now(); const entry = attempts.get(ip); if (!entry || now > entry.resetAt) { attempts.set(ip, { count: 1, resetAt: now + WINDOW_MS }); return false; } if (entry.count >= MAX_ATTEMPTS) return true; entry.count++; return false; }; const safeEqual = (a: string, b: string): boolean => { const aBuf = Buffer.from(a); const bBuf = Buffer.from(b); if (aBuf.length !== bBuf.length) { timingSafeEqual(aBuf, aBuf); return false; } return timingSafeEqual(aBuf, bBuf); }; export default defineEventHandler(async (event) => { const ip = getRequestIP(event) ?? "unknown"; if (isRateLimited(ip)) { throw createError({ statusCode: 429, message: "Too many attempts, try again later" }); } const body = await readValidatedBody(event, bodySchema.parse); const valid = safeEqual(body.username, env.ADMIN_USERNAME) && safeEqual(body.password, env.ADMIN_PASSWORD); if (!valid) { throw createError({ statusCode: 401, message: "Invalid credentials" }); } await setUserSession(event, { user: { username: body.username } }); });