Files
url_tracker_tool/apps/api/dist/middleware/rate-limit.middleware.js
Andrei 58f8093689 Rebrand from 'Redirect Intelligence v2' to 'URL Tracker Tool V2' throughout UI
- Updated all component headers and documentation
- Changed navbar and footer branding
- Updated homepage hero badge
- Modified page title in index.html
- Simplified footer text to 'Built with ❤️'
- Consistent V2 capitalization across all references
2025-08-19 19:12:23 +00:00

191 lines
7.8 KiB
JavaScript

"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