spb/tools-spboucher-ai
Public
1import { and, eq, gt, count, sql as dsql } from "drizzle-orm";2import { db, schema } from "@/db";3/** Login limiter backed by the DB: 5 failures / 15 min per IP or per e-mail, exponential lockout up to 60 min. */4export async function loginAllowed(ip: string, email: string): Promise<{ ok: true } | { ok: false; retryAfterSec: number }> {5 const since = new Date(Date.now() - 15 * 60_000);6 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)));7 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)));8 const failures = Math.max(byIp.n, byUser.n);9 if (failures < 5) return { ok: true };10 return { ok: false, retryAfterSec: Math.min(60, 2 ** (failures - 4)) * 60 };11}12export async function recordAttempt(ip: string, email: string | null, success: boolean) {13 await db.insert(schema.loginAttempts).values({ ip, email, success });14 if (Math.random() < 0.05) await db.delete(schema.loginAttempts).where(dsql`created_at < now() - interval '2 days'`);15}16