feat(api): enforce per-minute request rate limits
This commit is contained in:
@@ -34,6 +34,7 @@ export class ApiServer {
|
||||
private requestCounts = new Map<string, number>();
|
||||
private authFailureCounts = new Map<string, number>();
|
||||
private serverErrorCounts = new Map<string, number>();
|
||||
private rateLimitBuckets = new Map<string, { count: number; windowStart: number }>();
|
||||
|
||||
constructor(
|
||||
config: IApiConfig,
|
||||
@@ -152,6 +153,12 @@ export class ApiServer {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isRequestWithinRateLimit(req)) {
|
||||
this.sendError(res, 429, 'Rate limit exceeded', 'rate_limit_exceeded');
|
||||
this.recordRequest(path, res.statusCode);
|
||||
return;
|
||||
}
|
||||
|
||||
// Route request
|
||||
try {
|
||||
await this.router.route(req, res, path);
|
||||
@@ -352,6 +359,41 @@ export class ApiServer {
|
||||
}
|
||||
}
|
||||
|
||||
private isRequestWithinRateLimit(req: http.IncomingMessage): boolean {
|
||||
const configuredLimit = this.config.rateLimit;
|
||||
if (!configuredLimit || configuredLimit <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const key = this.getRateLimitKey(req);
|
||||
const now = Date.now();
|
||||
const windowMs = 60 * 1000;
|
||||
const bucket = this.rateLimitBuckets.get(key);
|
||||
|
||||
if (!bucket || now - bucket.windowStart >= windowMs) {
|
||||
this.rateLimitBuckets.set(key, { count: 1, windowStart: now });
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bucket.count >= configuredLimit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bucket.count += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
private getRateLimitKey(req: http.IncomingMessage): string {
|
||||
if (typeof req.headers.authorization === 'string') {
|
||||
const match = req.headers.authorization.match(/^Bearer\s+(.+)$/i);
|
||||
if (match) {
|
||||
return `api_key:${match[1]}`;
|
||||
}
|
||||
}
|
||||
|
||||
return `ip:${req.socket.remoteAddress || 'unknown'}`;
|
||||
}
|
||||
|
||||
private incrementMetric(metric: Map<string, number>, path: string): void {
|
||||
metric.set(path, (metric.get(path) || 0) + 1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user