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:
parent
4889ead690
commit
05419e9457
3 changed files with 53 additions and 0 deletions
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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
41
lib/rate-limit.ts
Normal 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
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue