"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.projectTrackingRateLimit = exports.orgTrackingRateLimit = exports.exportRateLimit = exports.bulkRateLimit = exports.trackingRateLimit = exports.legacyRateLimit = void 0; exports.createRateLimitMiddleware = createRateLimitMiddleware; exports.organizationKeyGenerator = organizationKeyGenerator; exports.projectKeyGenerator = projectKeyGenerator; exports.addRateLimitStatus = addRateLimitStatus; exports.requestLogger = requestLogger; exports.rateLimitErrorHandler = rateLimitErrorHandler; const rate_limit_service_1 = require("../services/rate-limit.service"); const header_redaction_service_1 = require("../services/header-redaction.service"); const logger_1 = require("../lib/logger"); function createRateLimitMiddleware(options) { const { type, keyGenerator = defaultKeyGenerator, skipSuccessfulRequests = false, skipFailedRequests = false, onLimitReached, } = options; return async (req, res, next) => { try { const key = keyGenerator(req); const userId = req.user?.id; const rateLimitInfo = await rate_limit_service_1.rateLimitService.checkRateLimit(type, key, userId); res.set({ 'X-RateLimit-Limit': rateLimitInfo.limit.toString(), 'X-RateLimit-Remaining': rateLimitInfo.remaining.toString(), 'X-RateLimit-Reset': rateLimitInfo.reset.toISOString(), 'X-RateLimit-Tier': rateLimitInfo.tier, }); if (userId && (type === 'tracking' || type === 'bulk')) { await rate_limit_service_1.rateLimitService.checkBurstLimit(userId); } logger_1.logger.debug('Rate limit check passed', { type, key: header_redaction_service_1.headerRedactionService.partiallyRedactValue(key), userId: userId ? header_redaction_service_1.headerRedactionService.partiallyRedactValue(userId) : undefined, remaining: rateLimitInfo.remaining, tier: rateLimitInfo.tier, userAgent: req.get('User-Agent'), ip: req.ip, }); next(); } catch (error) { if (error instanceof rate_limit_service_1.RateLimitError) { res.set({ 'X-RateLimit-Limit': '0', 'X-RateLimit-Remaining': '0', 'X-RateLimit-Reset': error.reset.toISOString(), 'X-RateLimit-Tier': error.tier, 'Retry-After': Math.ceil((error.reset.getTime() - Date.now()) / 1000).toString(), }); logger_1.logger.warn('Rate limit exceeded', { type, tier: error.tier, userId: req.user?.id, ip: req.ip, userAgent: req.get('User-Agent'), }); if (onLimitReached) { onLimitReached(req, res); return; } return res.status(429).json({ success: false, error: 'Rate limit exceeded', message: `Too many requests for ${error.tier} tier. Please try again later.`, retryAfter: error.reset.toISOString(), tier: error.tier, }); } if (error instanceof rate_limit_service_1.BurstLimitError) { res.set({ 'X-RateLimit-Type': 'burst', 'X-RateLimit-Tier': error.tier, 'Retry-After': '60', }); logger_1.logger.warn('Burst limit exceeded', { type, tier: error.tier, limit: error.limit, userId: req.user?.id, ip: req.ip, }); return res.status(429).json({ success: false, error: 'Burst limit exceeded', message: `Too many requests per minute for ${error.tier} tier (limit: ${error.limit}/min).`, retryAfter: new Date(Date.now() + 60000).toISOString(), tier: error.tier, }); } logger_1.logger.error('Rate limit middleware error:', error); next(error); } }; } function defaultKeyGenerator(req) { return req.user ? `user:${req.user.id}` : `ip:${req.ip}`; } function organizationKeyGenerator(req) { if (req.user?.memberships?.[0]?.orgId) { return `org:${req.user.memberships[0].orgId}`; } return defaultKeyGenerator(req); } function projectKeyGenerator(req) { const projectId = req.body?.projectId || req.params?.projectId || req.query?.projectId; if (projectId) { return `project:${projectId}`; } return defaultKeyGenerator(req); } function addRateLimitStatus(type) { return async (req, res, next) => { try { const key = defaultKeyGenerator(req); const userId = req.user?.id; const status = await rate_limit_service_1.rateLimitService.getRateLimitStatus(type, key, userId); res.set({ 'X-RateLimit-Limit': status.limit.toString(), 'X-RateLimit-Remaining': status.remaining.toString(), 'X-RateLimit-Reset': status.reset.toISOString(), 'X-RateLimit-Tier': status.tier, }); next(); } catch (error) { logger_1.logger.warn('Failed to add rate limit status headers:', error); next(); } }; } exports.legacyRateLimit = createRateLimitMiddleware({ type: 'legacy', }); exports.trackingRateLimit = createRateLimitMiddleware({ type: 'tracking', }); exports.bulkRateLimit = createRateLimitMiddleware({ type: 'bulk', }); exports.exportRateLimit = createRateLimitMiddleware({ type: 'export', }); exports.orgTrackingRateLimit = createRateLimitMiddleware({ type: 'tracking', keyGenerator: organizationKeyGenerator, }); exports.projectTrackingRateLimit = createRateLimitMiddleware({ type: 'tracking', keyGenerator: projectKeyGenerator, }); function requestLogger(options = {}) { const { includeBody = false, redactionLevel = 'full' } = options; return (req, res, next) => { const startTime = Date.now(); const redactedRequest = header_redaction_service_1.headerRedactionService.redactLogData({ method: req.method, url: req.url, headers: req.headers, body: includeBody ? req.body : undefined, user: req.user ? { id: req.user.id, email: header_redaction_service_1.headerRedactionService.partiallyRedactValue(req.user.email), } : undefined, ip: req.ip, userAgent: req.get('User-Agent'), }, { redactionLevel }); const originalEnd = res.end; res.end = function (chunk, encoding) { const duration = Date.now() - startTime; logger_1.logger.info('Request completed', { ...redactedRequest, statusCode: res.statusCode, duration, contentLength: res.get('Content-Length'), }); originalEnd.call(this, chunk, encoding); return this; }; next(); }; } function rateLimitErrorHandler(error, req, res, next) { if (error instanceof rate_limit_service_1.RateLimitError || error instanceof rate_limit_service_1.BurstLimitError) { return res.status(429).json({ success: false, error: 'Rate limit exceeded', message: error.message, }); } next(error); } //# sourceMappingURL=rate-limit.middleware.js.map