mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-07-26 12:08:15 +00:00
feat(server): raise event rate limit to 50 and name rate-limit constants
Raise the per-socket event budget from 30 to 50 per 10s to give legitimate bursts (episode joins, force-sync, ACK flurries) more headroom. Extract the previously inline connection/event/health/admin windows and limits into named constants (CONNECTION_RATE_LIMIT, EVENT_RATE_LIMIT, *_WINDOW_MS). Tests now derive their thresholds from the exported constants instead of hardcoding them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,9 @@ import {
|
||||
roomListCooldowns,
|
||||
rateLimitDenied,
|
||||
startRateLimitCleanup,
|
||||
stopRateLimitCleanup
|
||||
stopRateLimitCleanup,
|
||||
CONNECTION_RATE_LIMIT,
|
||||
EVENT_RATE_LIMIT
|
||||
} from '../server/rate-limiter.js';
|
||||
|
||||
// Helper: mock io for cleanup
|
||||
@@ -31,8 +33,9 @@ function reset() {
|
||||
// --- checkConnectionRate ---
|
||||
reset();
|
||||
assert.equal(checkConnectionRate('1.1.1.1'), true, 'first connection allowed');
|
||||
for (let i = 0; i < 9; i++) checkConnectionRate('1.1.1.1');
|
||||
assert.equal(checkConnectionRate('1.1.1.1'), false, '11th connection blocked');
|
||||
// Exhaust the rest of the budget (first call above counted as 1).
|
||||
for (let i = 0; i < CONNECTION_RATE_LIMIT - 1; i++) checkConnectionRate('1.1.1.1');
|
||||
assert.equal(checkConnectionRate('1.1.1.1'), false, `connection beyond ${CONNECTION_RATE_LIMIT}/window blocked`);
|
||||
assert.equal(rateLimitDenied.connections, 1, 'denial counter incremented');
|
||||
|
||||
reset();
|
||||
@@ -41,8 +44,9 @@ assert.equal(checkConnectionRate('2.2.2.2'), true, 'separate IP independent');
|
||||
// --- checkEventRate ---
|
||||
reset();
|
||||
assert.equal(checkEventRate('sock1'), true, 'first event allowed');
|
||||
for (let i = 0; i < 29; i++) checkEventRate('sock1');
|
||||
assert.equal(checkEventRate('sock1'), false, '31st event blocked');
|
||||
// Exhaust the rest of the budget (first call above counted as 1).
|
||||
for (let i = 0; i < EVENT_RATE_LIMIT - 1; i++) checkEventRate('sock1');
|
||||
assert.equal(checkEventRate('sock1'), false, `event beyond ${EVENT_RATE_LIMIT}/window blocked`);
|
||||
assert.equal(rateLimitDenied.events, 1);
|
||||
|
||||
reset();
|
||||
|
||||
+18
-10
@@ -7,6 +7,14 @@ export const ROOM_LIST_COOLDOWN_MS = 10000;
|
||||
export const HEALTH_RATE_LIMIT_PER_MINUTE = 10;
|
||||
export const ADMIN_METRICS_AUTH_RATE_LIMIT_PER_MINUTE = 5;
|
||||
|
||||
// --- Connection & event budgets (formerly inline magic numbers) ---
|
||||
export const CONNECTION_RATE_LIMIT = 10; // max new connections per IP per window
|
||||
export const CONNECTION_RATE_WINDOW_MS = 60000; // 1 minute
|
||||
export const EVENT_RATE_LIMIT = 50; // max relayed events per socket per window
|
||||
export const EVENT_RATE_WINDOW_MS = 10000; // 10 seconds
|
||||
export const HEALTH_RATE_WINDOW_MS = 60000; // 1 minute
|
||||
export const ADMIN_METRICS_AUTH_WINDOW_MS = 60000; // 1 minute
|
||||
|
||||
export const connectionCounts = new Map(); // ip -> { count, resetTime }
|
||||
export const failedAuthAttempts = new Map(); // Map<IP+RoomID, {count, lastAttempt}>
|
||||
export const eventCounts = new Map(); // socketId -> { count, resetTime }
|
||||
@@ -72,30 +80,30 @@ export function recordAuthFailure(ip, roomId) {
|
||||
|
||||
export function checkConnectionRate(ip) {
|
||||
const now = Date.now();
|
||||
const entry = connectionCounts.get(ip) || { count: 0, resetTime: now + 60000 };
|
||||
if (now > entry.resetTime) { entry.count = 0; entry.resetTime = now + 60000; }
|
||||
const entry = connectionCounts.get(ip) || { count: 0, resetTime: now + CONNECTION_RATE_WINDOW_MS };
|
||||
if (now > entry.resetTime) { entry.count = 0; entry.resetTime = now + CONNECTION_RATE_WINDOW_MS; }
|
||||
entry.count++;
|
||||
connectionCounts.set(ip, entry);
|
||||
if (entry.count <= 10) return true;
|
||||
if (entry.count <= CONNECTION_RATE_LIMIT) return true;
|
||||
rateLimitDenied.connections++;
|
||||
return false;
|
||||
}
|
||||
|
||||
export function checkEventRate(socketId) {
|
||||
const now = Date.now();
|
||||
const entry = eventCounts.get(socketId) || { count: 0, resetTime: now + 10000 };
|
||||
if (now > entry.resetTime) { entry.count = 0; entry.resetTime = now + 10000; }
|
||||
const entry = eventCounts.get(socketId) || { count: 0, resetTime: now + EVENT_RATE_WINDOW_MS };
|
||||
if (now > entry.resetTime) { entry.count = 0; entry.resetTime = now + EVENT_RATE_WINDOW_MS; }
|
||||
entry.count++;
|
||||
eventCounts.set(socketId, entry);
|
||||
if (entry.count <= 30) return true;
|
||||
if (entry.count <= EVENT_RATE_LIMIT) return true;
|
||||
rateLimitDenied.events++;
|
||||
return false;
|
||||
}
|
||||
|
||||
export function checkHealthRate(ip) {
|
||||
const now = Date.now();
|
||||
const entry = healthCounts.get(ip) || { count: 0, resetTime: now + 60000 };
|
||||
if (now > entry.resetTime) { entry.count = 0; entry.resetTime = now + 60000; }
|
||||
const entry = healthCounts.get(ip) || { count: 0, resetTime: now + HEALTH_RATE_WINDOW_MS };
|
||||
if (now > entry.resetTime) { entry.count = 0; entry.resetTime = now + HEALTH_RATE_WINDOW_MS; }
|
||||
entry.count++;
|
||||
healthCounts.set(ip, entry);
|
||||
if (entry.count <= HEALTH_RATE_LIMIT_PER_MINUTE) return true;
|
||||
@@ -105,8 +113,8 @@ export function checkHealthRate(ip) {
|
||||
|
||||
export function checkAdminMetricsAuthRate(ip) {
|
||||
const now = Date.now();
|
||||
const entry = adminMetricsAuthCounts.get(ip) || { count: 0, resetTime: now + 60000 };
|
||||
if (now > entry.resetTime) { entry.count = 0; entry.resetTime = now + 60000; }
|
||||
const entry = adminMetricsAuthCounts.get(ip) || { count: 0, resetTime: now + ADMIN_METRICS_AUTH_WINDOW_MS };
|
||||
if (now > entry.resetTime) { entry.count = 0; entry.resetTime = now + ADMIN_METRICS_AUTH_WINDOW_MS; }
|
||||
entry.count++;
|
||||
adminMetricsAuthCounts.set(ip, entry);
|
||||
if (entry.count <= ADMIN_METRICS_AUTH_RATE_LIMIT_PER_MINUTE) return true;
|
||||
|
||||
Reference in New Issue
Block a user