Index: src/middleware/rate-limit.js
===================================================================
--- src/middleware/rate-limit.js	(revision 2165b6d3d57ceaa8b5d1f70491868482149470cf)
+++ src/middleware/rate-limit.js	(revision 7bc636b391c66ac399c33e54f7173a022c6a3cbd)
@@ -13,28 +13,4 @@
 import rateLimit from 'express-rate-limit';
 import { renderPage } from './render.js';
-
-// Behind Cloudflare/Caddy, req.ip can arrive as "1.2.3.4:11046" (IPv4 with
-// port). express-rate-limit v7 validates the IP and otherwise throws
-// ERR_ERL_INVALID_IP_ADDRESS — uncaught async → the process crashes (and pm2
-// enters a restart loop). Strip a trailing IPv4 port, fall back to the
-// socket address, and leave IPv6 (multiple colons) untouched.
-function clientKey(req) {
-  let ip = req.ip || req.socket?.remoteAddress || '';
-  // Strip a trailing IPv4 port (1.2.3.4:11046 -> 1.2.3.4)
-  if (/^\d{1,3}(\.\d{1,3}){3}:\d+$/.test(ip)) ip = ip.split(':')[0];
-  // IPv6-mapped IPv4 (::ffff:1.2.3.4) -> the plain IPv4
-  const mapped = ip.match(/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/i);
-  if (mapped) return mapped[1];
-  // Real IPv6: key on the /64 network prefix, not the full address. A single
-  // user/allocation is usually a whole /64, so this stops an attacker from
-  // getting a fresh budget by rotating addresses within their own range.
-  if (ip.includes(':')) {
-    const left = ip.includes('::') ? ip.split('::')[0] : ip;
-    const groups = left.split(':').filter(Boolean);
-    while (groups.length < 4) groups.push('0');
-    return groups.slice(0, 4).join(':') + '::/64';
-  }
-  return ip || 'unknown';
-}
 
 function blockedHandler(viewName, bodyClass, friendlyMsg) {
@@ -66,6 +42,4 @@
   standardHeaders: true,
   legacyHeaders: false,
-  keyGenerator: clientKey,
-  validate: { ip: false },
   // Only count failed attempts. Successful logins don't burn the budget.
   skipSuccessfulRequests: true,
@@ -78,65 +52,5 @@
   standardHeaders: true,
   legacyHeaders: false,
-  keyGenerator: clientKey,
-  validate: { ip: false },
   skipSuccessfulRequests: false,   // any attempt counts (registration spam is the concern)
   handler: blockedHandler('pages/auth-register', 'on-special', 'Too many signup attempts.'),
 });
-
-// ─── Fediverse (/ap/*) ────────────────────────────────────────────
-// These endpoints are hit by REMOTE SERVERS, not browsers, so the default
-// plain-text 429 is the right response (no HTML page). Deliberately generous:
-// legitimate federation from one instance never comes close, but a flood from
-// a single IP is capped. Per-IP via the same /64-aware clientKey.
-
-// Baseline read cap across all /ap/* (actor, outbox, notes, webfinger, …).
-// 5 req/sec per IP — far above any real Mastodon polling.
-export const apReadLimiter = rateLimit({
-  windowMs: 60 * 1000,
-  max: 300,
-  standardHeaders: true,
-  legacyHeaders: false,
-  keyGenerator: clientKey,
-  validate: { ip: false },
-});
-
-// ─── OpenWebAuth /magic ───────────────────────────────────────────
-// Elke poging doet EEN RSA-ontsleuteling met de actorsleutel van een site. Dat
-// is precies de vorm waar een Bleichenbacher/Marvin-orakel op draait: veel
-// aangepaste ciphertexts, en uit de antwoorden de sleutel afleiden. De
-// ontsleuteling zelf is daartegen gehard (implicit rejection in
-// OpenWebAuthService.decryptToken), maar echte constant-time code bestaat niet
-// in JavaScript. Een grens op het AANTAL pogingen doet daarom het zware werk:
-// een orakel heeft er honderdduizenden nodig.
-//
-// TELT ALLE POGINGEN, niet alleen de mislukte. Een teller die alleen faalt
-// meetelt is zelf weer een orakel -- dan leest een aanvaller aan het knijpen af
-// of zijn padding klopte, en is de vertakking die we bij de ontsleuteling
-// weghaalden aan de achterdeur terug.
-//
-// Per SITE-SLUG, want dat is wat een sleutelpaar heeft (getOrCreateKeys(slug)):
-// de grens hoort bij de sleutel die beschermd wordt, niet bij het IP van de
-// eigenaar of bij de doel-host die de aanvaller zelf kiest.
-//
-// Twintig per uur is voor een mens onzichtbaar -- je klikt een handvol keer per
-// dag naar een andere site -- en voor een orakel dodelijk.
-export const owaMagicLimiter = rateLimit({
-  windowMs: 60 * 60 * 1000,
-  max: 20,
-  standardHeaders: true,
-  legacyHeaders: false,
-  keyGenerator: (req) => 'owa:' + String((req.body && req.body.slug) || (req.session && req.session.user && req.session.user.id) || 'onbekend'),
-  validate: { ip: false },
-});
-
-// Inbox POSTs each trigger an outbound actor fetch (signature verify) → cap the
-// amplification/queue-inflation a single source can drive. 120/min/IP is still
-// generous for a small site's inbound federation; bump if a busy instance trips it.
-export const apInboxLimiter = rateLimit({
-  windowMs: 60 * 1000,
-  max: 120,
-  standardHeaders: true,
-  legacyHeaders: false,
-  keyGenerator: clientKey,
-  validate: { ip: false },
-});
