Rate limiting & cron auth
Protection contre l'abus — limites par IP/user et auth des routes cron en constant-time.
Deux helpers utilitaires lib/rate-limit.ts et lib/cron-auth.ts.
Rate limit
lib/rate-limit.ts — bucket en mémoire-process, parfait pour single-instance.
import { checkRateLimit, clientIp } from "@/lib/rate-limit";
const ip = clientIp(req.headers);
const rl = checkRateLimit(`waitlist:${ip}`, 5, 60 * 60 * 1000); // 5/h
if (!rl.ok) {
return NextResponse.json(
{ error: "Trop de requêtes" },
{ status: 429, headers: { "Retry-After": Math.ceil(rl.retryAfterMs / 1000).toString() } },
);
}Routes déjà protégées :
| Route | Limite |
|---|---|
POST /api/waitlist | 5/h IP |
POST /api/auth/register | 10/h IP |
POST /api/auth/register/resend-code | 5/15min IP |
POST /api/auth/forgot-password | 5/h IP |
POST /api/auth/verify-email | 20/15min IP |
POST /api/auth/lock-status | 10/min IP |
POST /api/user/change-email | 5/h IP |
GET /api/admin/users/[id]/export | 10/h admin |
Multi-instance / serverless
Le cache est par instance. Sur Vercel serverless avec autoscaling, la
limite réelle = limit × N instances. Pour un cap absolu :
- Migre vers Upstash Redis (
@upstash/ratelimit) — pattern identique - Ou un middleware edge avec KV store
Cron auth
lib/cron-auth.ts — vérification constant-time du header Authorization
contre process.env.CRON_SECRET.
import { isAuthorizedCron } from "@/lib/cron-auth";
export async function POST(req: Request) {
if (!isAuthorizedCron(req.headers.get("authorization"))) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
// ...
}Compare via timingSafeEqual plutôt que === — évite la fuite du
secret par timing.
Routes déjà protégées :
/api/cron/coupon-ending/api/cron/purge-deleted-accounts
Côté planificateur
Set le header sur ton scheduler (Vercel Cron, Trigger.dev, etc.) :
Authorization: Bearer ${CRON_SECRET}Le secret doit être un random ≥ 32 chars (généré une fois, stocké en env). Ne jamais le logger.