Files
Emailsorter/server/middleware/rateLimit.mjs
ANDJ ecae89a79d fix(dev): Vite-API-Proxy, Auth, Stripe-Mails und Backend-Erweiterungen
- Client: API-Basis-URL (joinApiUrl, /v1-Falle), Vite strictPort + Proxy 127.0.0.1, Nicht-JSON-Fehler

- Server: /api-404 ohne Wildcard-Bug, SPA-Fallback, Auth-Middleware, Cron, Mailer, Crypto

- Routen: OAuth-State, Email/Stripe/Analytics; client/.env.example

Made-with: Cursor
2026-04-03 00:23:01 +02:00

105 lines
2.6 KiB
JavaScript

/**
* Rate Limiting Middleware
* Prevents abuse by limiting requests per IP/user
*/
import { RateLimitError } from './errorHandler.mjs'
import { isAdmin } from '../config/index.mjs'
// In-memory store for rate limiting (use Redis in production)
const requestCounts = new Map()
// Clean up old entries every minute
setInterval(() => {
const now = Date.now()
for (const [key, data] of requestCounts.entries()) {
if (now - data.windowStart > data.windowMs) {
requestCounts.delete(key)
}
}
}, 60000)
/**
* Create rate limiter middleware
* @param {Object} options - Rate limit options
* @param {number} options.windowMs - Time window in milliseconds
* @param {number} options.max - Max requests per window
* @param {string} options.message - Error message
* @param {Function} options.keyGenerator - Function to generate unique key
* @param {Function} options.skip - If (req) => true, do not count this request
*/
export function rateLimit(options = {}) {
const {
windowMs = 60000, // 1 minute
max = 100,
message = 'Zu viele Anfragen. Bitte versuche es später erneut.',
keyGenerator = (req) => req.ip,
skip = () => false,
} = options
return (req, res, next) => {
if (skip(req)) {
return next()
}
const key = keyGenerator(req)
const now = Date.now()
let data = requestCounts.get(key)
if (!data || now - data.windowStart > windowMs) {
data = { count: 0, windowStart: now, windowMs }
requestCounts.set(key, data)
}
data.count++
// Set rate limit headers
res.set({
'X-RateLimit-Limit': max,
'X-RateLimit-Remaining': Math.max(0, max - data.count),
'X-RateLimit-Reset': new Date(data.windowStart + windowMs).toISOString(),
})
if (data.count > max) {
return next(new RateLimitError(message))
}
next()
}
}
/**
* Pre-configured rate limiters
*/
export const limiters = {
// General API rate limit
api: rateLimit({
windowMs: 60000,
max: 100,
message: 'API Rate Limit überschritten',
}),
// Stricter limit for auth endpoints
auth: rateLimit({
windowMs: 900000, // 15 minutes
max: 10,
message: 'Zu viele Anmeldeversuche. Bitte warte 15 Minuten.',
}),
// Limit for email sorting (expensive operation); ADMIN_EMAILS (isAdmin) bypass
emailSort: rateLimit({
windowMs: 60000,
max: 30, // Erhöht für Entwicklung
message: 'E-Mail-Sortierung ist limitiert. Bitte warte eine Minute.',
skip: (req) => isAdmin(req.appwriteUser?.email),
}),
// Limit for AI operations
ai: rateLimit({
windowMs: 60000,
max: 20,
message: 'KI-Anfragen sind limitiert.',
}),
}