← back to Wine Finder Next
lib/rateLimit.ts
88 lines
// Rate Limiting Middleware
import { NextRequest } from 'next/server';
interface RateLimitConfig {
windowMs: number;
maxRequests: number;
}
const rateLimitStore = new Map<string, { count: number; resetAt: number }>();
// Different rate limits for different endpoints
const RATE_LIMITS: Record<string, RateLimitConfig> = {
'/api/membership/votes/': { windowMs: 60000, maxRequests: 10 }, // 10 votes per minute
'/api/membership/marketplace/': { windowMs: 60000, maxRequests: 20 }, // 20 market actions per minute
'/api/membership/bottles': { windowMs: 60000, maxRequests: 100 }, // 100 reads per minute
default: { windowMs: 60000, maxRequests: 60 } // 60 requests per minute default
};
export function getRateLimitConfig(path: string): RateLimitConfig {
for (const [key, config] of Object.entries(RATE_LIMITS)) {
if (path.startsWith(key)) {
return config;
}
}
return RATE_LIMITS.default;
}
export function checkRateLimit(
identifier: string,
config: RateLimitConfig
): { success: boolean; limit: number; remaining: number; reset: number } {
const now = Date.now();
const key = `${identifier}`;
const record = rateLimitStore.get(key);
// Clean up expired entries periodically
if (Math.random() < 0.01) {
cleanupExpiredEntries();
}
if (!record || now > record.resetAt) {
const resetAt = now + config.windowMs;
rateLimitStore.set(key, { count: 1, resetAt });
return {
success: true,
limit: config.maxRequests,
remaining: config.maxRequests - 1,
reset: resetAt
};
}
if (record.count >= config.maxRequests) {
return {
success: false,
limit: config.maxRequests,
remaining: 0,
reset: record.resetAt
};
}
record.count++;
return {
success: true,
limit: config.maxRequests,
remaining: config.maxRequests - record.count,
reset: record.resetAt
};
}
function cleanupExpiredEntries(): void {
const now = Date.now();
for (const [key, record] of rateLimitStore.entries()) {
if (now > record.resetAt) {
rateLimitStore.delete(key);
}
}
}
export function getClientIdentifier(request: NextRequest): string {
// Use multiple identifiers for better accuracy
const forwarded = request.headers.get('x-forwarded-for');
const realIp = request.headers.get('x-real-ip');
const ip = forwarded?.split(',')[0] || realIp || 'unknown';
// In production, also consider user session/auth token
return ip;
}