feat(api): add SSE subscriber isolation, token pruning and E2E rate-limit guard

- event-bus: wrap each subscriber.fn call in try/catch so one throwing subscriber cannot kill delivery to all others
- event-bus: log Redis parse errors instead of swallowing them silently; add .catch() on Redis publish promise for async fallback to local delivery
- pruning.ts: new runPruning() deletes expired invite tokens, expired password-reset tokens, and read notifications older than 90 days
- settings.runPruning: expose pruning as adminProcedure mutation
- trpc.ts: E2E_TEST_MODE rate-limit bypass is now a no-op in production (NODE_ENV=production); logs a startup warning if misconfigured

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-09 08:35:39 +02:00
parent 472d87c829
commit 60d267fa0a
4 changed files with 56 additions and 5 deletions
+11 -4
View File
@@ -90,7 +90,11 @@ function matchesSubscription(event: SseEvent, subscription: Subscription): boole
function deliverEvent(event: SseEvent): void {
for (const subscription of subscribers) {
if (matchesSubscription(event, subscription)) {
subscription.fn(event);
try {
subscription.fn(event);
} catch (err) {
logger.warn({ err, eventType: event.type }, "SSE subscriber threw during event delivery");
}
}
}
}
@@ -216,8 +220,8 @@ function setupSubscriber(): void {
timestamp: parsed.timestamp,
audience: canonicalizeSseAudiences(parsed.audience),
});
} catch {
// ignore parse errors
} catch (err) {
logger.warn({ err, message }, "Failed to parse SSE Redis message");
}
});
} catch (e) {
@@ -257,7 +261,10 @@ class EventBus {
timestamp: normalizedEvent.timestamp,
audience: normalizedEvent.audience,
}),
);
).catch((e: unknown) => {
logger.warn({ err: e, redisUrl: REDIS_URL, channel: CHANNEL }, "Redis publish promise rejected, falling back to local-only SSE delivery");
publishLocal(normalizedEvent);
});
} catch (e) {
logger.warn({ err: e, redisUrl: REDIS_URL, channel: CHANNEL }, "Redis publish failed, falling back to local-only SSE delivery");
// Deliver locally when Redis is unavailable