express-limiter-pro
Version:
A TypeScript library for safe display and sanitization to prevent XSS attacks.
1 lines • 14 kB
Source Map (JSON)
{"version":3,"sources":["../src/limits/limiters.ts","../src/utils/ipKeyHelpers.ts","../src/configs/rateLimitConfigs.ts","../src/logger/logger.ts","../src/configs/configManager.ts","../src/errors/error.ts","../src/configs/apiEndpoints.ts","../src/setup/rateLimitSetup.ts"],"sourcesContent":["import { Request, Response, RequestHandler } from 'express';\r\nimport { rateLimit } from 'express-rate-limit';\r\nimport { rateLimitConfigs } from '../configs/rateLimitConfigs';\r\nimport { RateLimitOptions } from '../types/types';\r\nimport { logger } from '../logger/logger';\r\nimport { configManager } from '../configs/configManager';\r\n\r\nlet apiLimiter: RequestHandler | null = null;\r\nlet authLimiter: RequestHandler | null = null;\r\nlet sensitiveLimiter: RequestHandler | null = null;\r\n\r\nexport const createLimiter = (options: RateLimitOptions): RequestHandler =>\r\n rateLimit({\r\n ...options,\r\n skipSuccessfulRequests: false,\r\n handler: (req: Request, res: Response) => {\r\n logger.warn(`Rate limit exceeded for ${req.ip} on ${req.originalUrl}`);\r\n return res.status(429).json({\r\n success: false,\r\n error: options.errorName || 'Rate Limit Exceeded',\r\n message: options.message || 'Too many requests, please try again later.'\r\n });\r\n }\r\n });\r\n\r\nexport const initializeRateLimiters = async (): Promise<void> => {\r\n try {\r\n const config = await configManager.getConfig();\r\n const rateLimitConfig = rateLimitConfigs[config?.isDevelopment ? 'development' : 'production'];\r\n\r\n apiLimiter = createLimiter({\r\n ...rateLimitConfig.api,\r\n errorName: 'API Rate Limit Exceeded',\r\n message: 'Too many requests, please try again later.'\r\n });\r\n\r\n authLimiter = createLimiter({\r\n ...rateLimitConfig.auth,\r\n errorName: 'Authentication Rate Limit Exceeded',\r\n message: 'Too many authentication attempts, please try again later.'\r\n });\r\n\r\n sensitiveLimiter = createLimiter({\r\n ...rateLimitConfig.sensitive,\r\n errorName: 'Operation Rate Limit Exceeded',\r\n message: 'Too many sensitive operations attempted, please try again later.'\r\n });\r\n\r\n logger.info('Rate limiters initialized successfully');\r\n } catch (error) {\r\n logger.error('Failed to initialize rate limiters:', error);\r\n throw error;\r\n }\r\n};\r\n\r\nexport const getApiLimiter = (): RequestHandler => {\r\n if (!apiLimiter) throw new Error('Rate limiters not initialized. Call initializeRateLimiters() first.');\r\n return apiLimiter;\r\n};\r\n\r\nexport const getAuthLimiter = (): RequestHandler => {\r\n if (!authLimiter) throw new Error('Rate limiters not initialized. Call initializeRateLimiters() first.');\r\n return authLimiter;\r\n};\r\n\r\nexport const getSensitiveLimiter = (): RequestHandler => {\r\n if (!sensitiveLimiter) throw new Error('Rate limiters not initialized. Call initializeRateLimiters() first.');\r\n return sensitiveLimiter;\r\n};\r\n","import { Request } from 'express';\r\nimport { ipKeyGenerator } from 'express-rate-limit';\r\n\r\nexport function getIpFromRequest(req: Request): string {\r\n const xff = (req.headers['x-forwarded-for'] as string | undefined) ?? '';\r\n const forwarded = xff.split(',')[0]?.trim();\r\n\r\n const stripPort = (ip: string) => ip.replace(/:\\d+$/, '');\r\n\r\n if (req.ip) return stripPort(req.ip);\r\n if (forwarded) return stripPort(forwarded);\r\n return '127.0.0.1';\r\n}\r\n\r\n/** ipv6Subnet: false disables subnet masking, number e.g. 64 applies /64 masking */\r\nexport function ipKeyGeneratorFromReq(req: Request, ipv6Subnet: number | false = 64): string {\r\n const ip = getIpFromRequest(req);\r\n return ipKeyGenerator(ip, ipv6Subnet);\r\n}\r\n","import { Request } from 'express';\r\nimport { RateLimitConfig } from '../types/types';\r\nimport { ipKeyGeneratorFromReq } from '../utils/ipKeyHelpers';\r\n\r\nexport const rateLimitConfigs: Record<string, RateLimitConfig> = {\r\n development: {\r\n api: { windowMs: 15 * 60 * 1000, max: 1000, standardHeaders: true, legacyHeaders: false },\r\n auth: { windowMs: 15 * 60 * 1000, max: 50, standardHeaders: true, legacyHeaders: false },\r\n sensitive: { windowMs: 15 * 60 * 1000, max: 20, standardHeaders: true, legacyHeaders: false }\r\n },\r\n production: {\r\n api: {\r\n windowMs: 15 * 60 * 1000,\r\n max: 300,\r\n standardHeaders: true,\r\n legacyHeaders: false,\r\n keyGenerator: (req: Request) => ipKeyGeneratorFromReq(req) \r\n },\r\n auth: {\r\n windowMs: 15 * 60 * 1000,\r\n max: 20,\r\n standardHeaders: true,\r\n legacyHeaders: false,\r\n keyGenerator: (req: Request) => ipKeyGeneratorFromReq(req)\r\n },\r\n sensitive: {\r\n windowMs: 60 * 60 * 1000,\r\n max: 10,\r\n standardHeaders: true,\r\n legacyHeaders: false,\r\n keyGenerator: (req: Request) => ipKeyGeneratorFromReq(req)\r\n }\r\n }\r\n};\r\n","export const logger = {\r\n info: (...args: unknown[]) => console.info('[info]', ...args),\r\n warn: (...args: unknown[]) => console.warn('[warn]', ...args),\r\n error: (...args: unknown[]) => console.error('[error]', ...args),\r\n debug: (...args: unknown[]) => console.debug('[debug]', ...args)\r\n};\r\n\r\nexport default logger;","import { EventEmitter } from \"events\";\r\nimport { TokenSecret, ConfigSource } from \"../types/types\";\r\nimport { ConfigNotFoundException } from \"../errors/error\";\r\n\r\nexport class ConfigManager extends EventEmitter {\r\n private config?: TokenSecret;\r\n private configSource?: ConfigSource;\r\n\r\n constructor() {\r\n super();\r\n }\r\n\r\n setConfigSource(source: ConfigSource): void {\r\n this.configSource = source;\r\n }\r\n\r\n setConfig(cfg: TokenSecret): void {\r\n this.config = { ...cfg };\r\n this.emit(\"configAvailable\", this.config);\r\n }\r\n\r\n getConfig(): TokenSecret | undefined {\r\n return this.config;\r\n }\r\n\r\n async loadConfig(): Promise<TokenSecret> {\r\n if (!this.configSource) {\r\n throw new ConfigNotFoundException(\"Config source not set\");\r\n }\r\n try {\r\n const config = await this.configSource();\r\n this.setConfig(config);\r\n return config;\r\n } catch (error) {\r\n throw new ConfigNotFoundException(\"Error loading configuration\");\r\n }\r\n }\r\n}\r\n\r\nexport const configManager = new ConfigManager();","export class ConfigNotFoundException extends Error {\r\n constructor(message?: string) {\r\n super(message ?? 'Configuration not found');\r\n this.name = 'ConfigNotFoundException';\r\n }\r\n}\r\n","// -----------------------------------------------------------------------------\r\n// API Endpoint Configuration\r\n// -----------------------------------------------------------------------------\r\nexport const API_ENDPOINT_HEADER = 'X-API-Endpoint';\r\n\r\nexport const ENDPOINT_MAP: Record<string, string> = {\r\n 'auth:register': '/register',\r\n 'auth:login': '/login',\r\n 'auth:admin-login': '/restricted-access/authenticate',\r\n 'auth:logout': '/logout/:id',\r\n 'auth:refresh-token': '/refresh-token',\r\n 'auth:2fa-setup': '/2fa/setup',\r\n 'auth:2fa-enable': '/2fa/enable',\r\n 'auth:2fa-verify': '/2fa/verify',\r\n 'auth:2fa-disable': '/2fa/disable'\r\n};\r\n\r\nexport const SENSITIVE_ENDPOINTS: Record<string, boolean> = {\r\n 'auth:2fa-setup': true,\r\n 'auth:2fa-enable': true,\r\n 'auth:2fa-disable': true,\r\n 'auth:2fa-verify': true,\r\n 'auth:admin-login': true,\r\n 'auth:refresh-token': true\r\n};\r\n\r\nexport const PARAM_MAPPING: Record<string, string[]> = {\r\n 'auth:logout': ['id']\r\n};\r\n\r\nexport const PUBLIC_PATHS = ['/login', '/register'];\r\nexport const PUBLIC_ENDPOINTS = ['auth:login', 'auth:register'];\r\n","import { Application, RequestHandler } from 'express';\r\nimport { getApiLimiter, getAuthLimiter, getSensitiveLimiter, initializeRateLimiters } from '../limits/limiters';\r\nimport { logger } from '../logger/logger';\r\nimport { RateLimitRule } from '../types/types';\r\nimport { API_ENDPOINT_HEADER } from '../configs/apiEndpoints';\r\n\r\nexport async function configureRateLimits(app: Application): Promise<void> {\r\n try {\r\n await initializeRateLimiters()\r\n const apiLimiter = getApiLimiter();\r\n const authLimiter = getAuthLimiter();\r\n const sensitiveLimiter = getSensitiveLimiter();\r\n\r\n const rateLimitRules: RateLimitRule[] = [\r\n { paths: ['/api', '/api/v1'], limiter: apiLimiter },\r\n {\r\n paths: ['/api/auth', '/api/v1/auth'],\r\n limiter: authLimiter,\r\n condition: (req) => ['/login', '/register', '/authenticate'].some(p => req.path.includes(p))\r\n },\r\n {\r\n paths: ['/api'],\r\n limiter: sensitiveLimiter,\r\n condition: (req) => req.path.includes('/2fa') ||\r\n ['auth:2fa-setup', 'auth:2fa-enable', 'auth:2fa-disable', 'auth:2fa-verify']\r\n .includes((req.headers[API_ENDPOINT_HEADER] || req.headers[API_ENDPOINT_HEADER.toLowerCase()]) as string)\r\n }\r\n ];\r\n\r\n rateLimitRules.forEach(rule => {\r\n if (rule.condition) {\r\n const middleware: RequestHandler = (req, res, next) =>\r\n rule.condition!(req) ? rule.limiter(req, res, next) : next();\r\n app.use(rule.paths, middleware);\r\n } else {\r\n app.use(rule.paths, rule.limiter);\r\n }\r\n });\r\n\r\n logger.info('Rate limiting configured successfully');\r\n } catch (error) {\r\n logger.error('Failed to configure rate limiting:', error);\r\n throw error;\r\n }\r\n}\r\n"],"mappings":"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","names":["apiLimiter","authLimiter","sensitiveLimiter"]}