59 lines
2.0 KiB
TypeScript
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),
|
|
})
|