feat: add rate limiting to login and quote-request endpoints

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
mikl0s 2026-02-22 21:55:40 +00:00
parent 4889ead690
commit 05419e9457
3 changed files with 53 additions and 0 deletions

View file

@ -1,7 +1,13 @@
import { NextRequest, NextResponse } from "next/server"
import { login } from "@/lib/auth"
import { rateLimit } from "@/lib/rate-limit"
export async function POST(request: NextRequest) {
const ip = request.headers.get("x-forwarded-for") || "unknown"
if (!rateLimit(ip, 5, 60_000)) {
return NextResponse.json({ error: "For mange forsøg. Prøv igen om lidt." }, { status: 429 })
}
try {
const { email, password } = await request.json()

View file

@ -3,6 +3,7 @@ import nodemailer from "nodemailer"
import { formatPrice, type CalculationDetails } from "@/lib/calculations"
import { FLOORING_TYPES } from "@/lib/constants"
import { saveQuote } from "@/lib/db"
import { rateLimit } from "@/lib/rate-limit"
interface QuoteRequestBody {
customerInfo: {
@ -234,6 +235,11 @@ function formatFoamKingEmail(
}
export async function POST(request: NextRequest) {
const ip = request.headers.get("x-forwarded-for") || "unknown"
if (!rateLimit(ip, 10, 60_000)) {
return NextResponse.json({ error: "For mange anmodninger. Prøv igen om lidt." }, { status: 429 })
}
try {
const body: QuoteRequestBody = await request.json()
const { customerInfo, calculationDetails } = body

41
lib/rate-limit.ts Normal file
View file

@ -0,0 +1,41 @@
// Simple in-memory rate limiter. No external dependencies.
// Tracks requests per IP with a sliding window.
interface RateLimitEntry {
count: number
resetAt: number
}
const store = new Map<string, RateLimitEntry>()
// Clean up expired entries periodically
setInterval(() => {
const now = Date.now()
for (const [key, entry] of store) {
if (entry.resetAt < now) store.delete(key)
}
}, 60_000)
/**
* Check if a request should be rate-limited.
* @param key - Unique identifier (e.g. IP address)
* @param limit - Max requests per window
* @param windowMs - Time window in milliseconds
* @returns true if the request is allowed, false if rate-limited
*/
export function rateLimit(key: string, limit: number, windowMs: number): boolean {
const now = Date.now()
const entry = store.get(key)
if (!entry || entry.resetAt < now) {
store.set(key, { count: 1, resetAt: now + windowMs })
return true
}
if (entry.count >= limit) {
return false
}
entry.count++
return true
}