add passenger telemetry
This commit is contained in:
83
backend/src/middleware/passengerTelemetryRateLimiter.js
Normal file
83
backend/src/middleware/passengerTelemetryRateLimiter.js
Normal file
@@ -0,0 +1,83 @@
|
||||
const { hashValue } = require('../services/passengerTelemetryAggregator');
|
||||
|
||||
const buckets = new Map();
|
||||
|
||||
function cleanupBucket(key, now) {
|
||||
const bucket = buckets.get(key);
|
||||
|
||||
if (bucket && bucket.resetAt <= now) {
|
||||
buckets.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
function incrementBucket(key, now, windowMs) {
|
||||
cleanupBucket(key, now);
|
||||
|
||||
const existing = buckets.get(key);
|
||||
|
||||
if (existing) {
|
||||
existing.count += 1;
|
||||
return existing;
|
||||
}
|
||||
|
||||
const bucket = {
|
||||
count: 1,
|
||||
resetAt: now + windowMs
|
||||
};
|
||||
|
||||
buckets.set(key, bucket);
|
||||
return bucket;
|
||||
}
|
||||
|
||||
function normalizeKeyPart(value, fallback) {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return hashValue(String(value).trim());
|
||||
}
|
||||
|
||||
function createPassengerTelemetryRateLimiter({
|
||||
maxRequests = 60,
|
||||
windowMs = 60 * 1000,
|
||||
nowProvider = () => Date.now()
|
||||
} = {}) {
|
||||
return (req, res, next) => {
|
||||
const now = nowProvider();
|
||||
const sessionId = req.body?.session_id ?? req.body?.sessionId;
|
||||
const sessionKey = normalizeKeyPart(sessionId, 'missing-session');
|
||||
const ipKey = normalizeKeyPart(req.ip || req.socket?.remoteAddress, 'unknown-ip');
|
||||
|
||||
const keys = [
|
||||
`passenger-telemetry:session:${sessionKey}`,
|
||||
`passenger-telemetry:ip:${ipKey}`
|
||||
];
|
||||
|
||||
if (req.apiKeyInfo?.id) {
|
||||
keys.push(`passenger-telemetry:api-key:${normalizeKeyPart(req.apiKeyInfo.id, 'api-key')}`);
|
||||
}
|
||||
|
||||
const exceeded = keys
|
||||
.map((key) => incrementBucket(key, now, windowMs))
|
||||
.find((bucket) => bucket.count > maxRequests);
|
||||
|
||||
if (exceeded) {
|
||||
return res.status(429).json({
|
||||
error: 'Too many passenger telemetry samples',
|
||||
message: 'Rate limit exceeded. Please try again later.',
|
||||
retryAfter: Math.ceil((exceeded.resetAt - now) / 1000)
|
||||
});
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
||||
}
|
||||
|
||||
function resetPassengerTelemetryRateLimitersForTesting() {
|
||||
buckets.clear();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createPassengerTelemetryRateLimiter,
|
||||
resetPassengerTelemetryRateLimitersForTesting
|
||||
};
|
||||
Reference in New Issue
Block a user