- 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
191 lines
7.8 KiB
JavaScript
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
|