From 069a1525ea19ffc666bc2f9468a42607a64960a8 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Sat, 25 Apr 2026 21:28:44 +0200 Subject: [PATCH] feat(federation): per-peer rate limiter for user lookup (60/min) --- packages/server/src/routes/federation.ts | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/server/src/routes/federation.ts b/packages/server/src/routes/federation.ts index 11136582..ed07a5a1 100644 --- a/packages/server/src/routes/federation.ts +++ b/packages/server/src/routes/federation.ts @@ -136,6 +136,32 @@ function isRelayRateLimited(peerOrigin: string): boolean { return false; } +// ─── In-memory rate limiter for the user-lookup endpoint (per-peer) ────────── +const lookupRateBuckets = new Map(); +const LOOKUP_RATE_WINDOW_MS = 60_000; +const LOOKUP_RATE_MAX = 60; + +function isLookupRateLimited(peerOrigin: string): boolean { + const now = Date.now(); + let timestamps = lookupRateBuckets.get(peerOrigin); + if (!timestamps) { + timestamps = []; + lookupRateBuckets.set(peerOrigin, timestamps); + } + const cutoff = now - LOOKUP_RATE_WINDOW_MS; + while (timestamps.length > 0 && (timestamps[0] ?? Infinity) < cutoff) { + timestamps.shift(); + } + if (timestamps.length >= LOOKUP_RATE_MAX) return true; + timestamps.push(now); + return false; +} + +// Test-only export — used by federation.userLookup.test.ts to reset between cases. +export function _resetLookupRateBuckets(): void { + lookupRateBuckets.clear(); +} + // ─── In-memory rate limiter for the ensure endpoint (per-user) ───────────── const ensureRateBuckets = new Map(); const ENSURE_RATE_WINDOW_MS = 15 * 60_000; // 15 minutes