Rate-Limit-Liste: bereits freigegebene IPs ausblenden

Die Liste basiert auf unveränderlichen SecurityEvents – ein Reset
leerte nur den In-Memory-Limiter, aber die historischen Events
blieben weitere 15 Min in der Anzeige stehen ("Freigeben klappt nicht").

Fix: für jede candidate-IP wird der letzte AuditLog-Eintrag
(resourceType=RateLimit) im 15-Min-Fenster geprüft. Liegt er nach dem
letzten Hit der IP, fliegt die IP aus der Liste – aber sobald wieder
ein RATE_LIMIT_HIT nach dem Reset kommt, taucht die IP wieder auf.

Live-verifiziert: trigger → 1 Eintrag; reset → 0 Einträge;
erneuter trigger → 1 Eintrag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-17 01:26:12 +02:00
parent 956bc394b8
commit c744eebfa3
@@ -61,6 +61,37 @@ export async function getActiveRateLimits(req: AuthRequest, res: Response): Prom
}
}
// Bereits manuell freigegebene IPs aus der Anzeige rauswerfen: wenn der
// letzte Reset (= Audit-Log-Eintrag) NACH dem letzten Hit liegt, ist die
// IP nicht mehr gesperrt. SecurityEvents sind unveränderlich, also brauchen
// wir diesen Reset-Marker, sonst bleibt eine bereits freigegebene IP
// weiterhin im Bildschirm hängen, bis das 15-Min-Fenster abgelaufen ist.
const candidateIps = Array.from(byIp.keys());
if (candidateIps.length > 0) {
const recentResets = await prisma.auditLog.findMany({
where: {
resourceType: 'RateLimit',
resourceId: { in: candidateIps },
createdAt: { gte: since },
},
select: { resourceId: true, createdAt: true },
orderBy: { createdAt: 'desc' },
});
const resetMap = new Map<string, Date>();
for (const r of recentResets) {
if (r.resourceId && !resetMap.has(r.resourceId)) {
resetMap.set(r.resourceId, r.createdAt);
}
}
for (const ip of candidateIps) {
const reset = resetMap.get(ip);
const entry = byIp.get(ip)!;
if (reset && reset >= entry.lastHit) {
byIp.delete(ip);
}
}
}
const list = Array.from(byIp.values()).sort(
(a, b) => b.lastHit.getTime() - a.lastHit.getTime(),
);