diff --git a/app/api/familyguard-flyer/route.ts b/app/api/familyguard-flyer/route.ts index 9cc48fd..af767ec 100644 --- a/app/api/familyguard-flyer/route.ts +++ b/app/api/familyguard-flyer/route.ts @@ -4,11 +4,42 @@ import { createServiceClient } from "@/lib/supabase"; const FLYER_DOWNLOAD_URL = "/downloads/MBO_FamilyGuard_Flyer_01.pdf"; +const RATE_LIMIT_WINDOW_MS = 60 * 60 * 1000; +const RATE_LIMIT_MAX = 3; +const requestLog = new Map(); + +function isRateLimited(ip: string): boolean { + const now = Date.now(); + const timestamps = (requestLog.get(ip) ?? []).filter( + (t) => now - t < RATE_LIMIT_WINDOW_MS + ); + if (timestamps.length >= RATE_LIMIT_MAX) { + requestLog.set(ip, timestamps); + return true; + } + timestamps.push(now); + requestLog.set(ip, timestamps); + return false; +} + +function getClientIp(request: Request): string { + const forwarded = request.headers.get("x-forwarded-for"); + if (forwarded) return forwarded.split(",")[0].trim(); + return "unknown"; +} + function isValidEmail(email: string): boolean { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); } export async function POST(request: Request) { + if (isRateLimited(getClientIp(request))) { + return NextResponse.json( + { ok: false, error: "Zu viele Anfragen. Bitte versuchen Sie es später erneut." }, + { status: 429 } + ); + } + let body: { email?: string; dsgvoEinwilligung?: boolean }; try { body = await request.json(); @@ -16,7 +47,7 @@ export async function POST(request: Request) { return NextResponse.json({ ok: false, error: "Ungültige Anfrage" }, { status: 400 }); } - const email = body.email?.trim() ?? ""; + const email = typeof body.email === "string" ? body.email.trim() : ""; if (!isValidEmail(email)) { return NextResponse.json( { ok: false, error: "Bitte geben Sie eine gültige E-Mail-Adresse ein" },