Closer/server/src/middleware/rateLimiter.ts

59 lines
2.0 KiB
TypeScript

import rateLimit from 'express-rate-limit'
import { getRateLimit } from '../config/env'
/**
* Skip rate limiting for successful requests from localhost (development)
*/
const skipLocalhost = (req: any): boolean => {
return req.socket?.remoteAddress?.includes('127.0.0.1') ||
req.socket?.remoteAddress?.includes('::1') ||
req.ip?.includes('127.0.0.1') ||
req.ip?.includes('::1')
}
// Get rate limit values from env vars (with defaults)
const WEBHOOK_LIMIT = getRateLimit('RATE_LIMIT_WEBHOOK', 10)
const HEALTH_LIMIT = getRateLimit('RATE_LIMIT_HEALTH', 30)
const DEFAULT_LIMIT = getRateLimit('RATE_LIMIT_DEFAULT', 60)
/**
* Webhook limiter: 10 requests per minute per IP (configurable via RATE_LIMIT_WEBHOOK)
* RevenueCat retries are spaced minutes apart, so this is generous.
* Successful webhooks (HTTP 200) DO count toward the limit — a processed
* webhook should not give an attacker unlimited free requests.
*/
export const webhookLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: WEBHOOK_LIMIT,
standardHeaders: true,
legacyHeaders: false,
keyGenerator: (req) => req.ip || req.socket?.remoteAddress || 'unknown',
skip: (req) => skipLocalhost(req),
})
/**
* Health limiter: 30 requests per minute per IP (configurable via RATE_LIMIT_HEALTH)
* Allows frequent health checks without abuse.
*/
export const healthLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: HEALTH_LIMIT,
standardHeaders: true,
legacyHeaders: false,
keyGenerator: (req) => req.ip || req.socket?.remoteAddress || 'unknown',
skip: (req) => skipLocalhost(req),
})
/**
* Default/API limiter: 60 requests per minute per IP (configurable via RATE_LIMIT_DEFAULT)
* For future authenticated routes.
*/
export const defaultLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: DEFAULT_LIMIT,
standardHeaders: true,
legacyHeaders: false,
keyGenerator: (req) => req.ip || req.socket?.remoteAddress || 'unknown',
skip: (req) => skipLocalhost(req),
})