diff --git a/backend/src/controllers/rateLimitAdmin.controller.ts b/backend/src/controllers/rateLimitAdmin.controller.ts index 573235bb..472600f2 100644 --- a/backend/src/controllers/rateLimitAdmin.controller.ts +++ b/backend/src/controllers/rateLimitAdmin.controller.ts @@ -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(); + 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(), );