import { and, eq, gt, count, sql as dsql } from "drizzle-orm"; import { db, schema } from "@/db"; /** Login limiter backed by the DB: 5 failures / 15 min per IP or per e-mail, exponential lockout up to 60 min. */ export async function loginAllowed(ip: string, email: string): Promise<{ ok: true } | { ok: false; retryAfterSec: number }> { const since = new Date(Date.now() - 15 * 60_000); const [byIp] = await db.select({ n: count() }).from(schema.loginAttempts).where(and(eq(schema.loginAttempts.ip, ip), eq(schema.loginAttempts.success, false), gt(schema.loginAttempts.createdAt, since))); const [byUser] = await db.select({ n: count() }).from(schema.loginAttempts).where(and(eq(schema.loginAttempts.email, email), eq(schema.loginAttempts.success, false), gt(schema.loginAttempts.createdAt, since))); const failures = Math.max(byIp.n, byUser.n); if (failures < 5) return { ok: true }; return { ok: false, retryAfterSec: Math.min(60, 2 ** (failures - 4)) * 60 }; } export async function recordAttempt(ip: string, email: string | null, success: boolean) { await db.insert(schema.loginAttempts).values({ ip, email, success }); if (Math.random() < 0.05) await db.delete(schema.loginAttempts).where(dsql`created_at < now() - interval '2 days'`); }