fix(protection): cooldown-elapse must set protectionDisabledAt in state endpoint

Bug: nach Cooldown-Ablauf reaktivierte sich der Schutz automatisch.

Root-Cause: Race zwischen zwei Endpoints die abgelaufene Cooldowns auflösen.
/api/cooldown/status setzt korrekt profile.protectionDisabledAt. /api/
protection/state — alle 5s während Cooldown gepollt — machte beim Ablauf
NUR resolveCooldown(), ohne protectionDisabledAt. state.get gewann das Race
fast immer → protectionDisabledAt blieb null → protectionShouldBeActive=true
→ Frontend-Bypass-Detection reaktivierte den Schutz.

Fix: state.get.ts setzt im expired-Branch ebenfalls protectionDisabledAt +
cooldownJustResolved-Flag.
This commit is contained in:
chahinebrini 2026-05-20 04:17:24 +02:00
parent c32eeeb070
commit 34005803da

View File

@ -1,6 +1,7 @@
import { requireUser } from "../../utils/auth"; import { requireUser } from "../../utils/auth";
import { getActiveCooldown, resolveCooldown } from "../../db/cooldown"; import { getActiveCooldown, resolveCooldown } from "../../db/cooldown";
import { getProfile } from "../../db/profile"; import { getProfile } from "../../db/profile";
import { usePrisma } from "../../utils/prisma";
/** /**
* GET /api/protection/state * GET /api/protection/state
@ -18,11 +19,26 @@ export default defineEventHandler(async (event) => {
let active = false; let active = false;
let remainingSeconds = 0; let remainingSeconds = 0;
let cooldownEndsAt: string | null = null; let cooldownEndsAt: string | null = null;
// True wenn dieser Request gerade einen abgelaufenen Cooldown resolved hat.
let cooldownJustResolved = false;
if (cooldown) { if (cooldown) {
const expired = now >= cooldown.cooldownEndsAt; const expired = now >= cooldown.cooldownEndsAt;
if (expired) { if (expired) {
await resolveCooldown(cooldown.id); await resolveCooldown(cooldown.id);
// Anti-Auto-Reactivation: Cooldown wurde durchgehalten → Schutz bleibt
// jetzt AUS, User muss explizit reaktivieren. MUSS hier passieren —
// dieser Endpoint wird alle 5s während Cooldown gepollt und gewinnt das
// Race gegen /api/cooldown/status fast immer. Ohne dieses Update bliebe
// protectionDisabledAt null → protectionShouldBeActive=true → Frontend-
// Bypass-Detection würde den Schutz automatisch wieder anschalten.
await usePrisma()
.profile.update({
where: { id: user.id },
data: { protectionDisabledAt: new Date() },
})
.catch(() => {});
cooldownJustResolved = true;
// After resolve: no active cooldown // After resolve: no active cooldown
} else { } else {
active = true; active = true;
@ -42,7 +58,8 @@ export default defineEventHandler(async (event) => {
// (protectionDisabledAt gesetzt) → Frontend macht KEINE Auto-Reactivation, // (protectionDisabledAt gesetzt) → Frontend macht KEINE Auto-Reactivation,
// User muss explizit re-aktivieren via /api/protection/mark-active. // User muss explizit re-aktivieren via /api/protection/mark-active.
// - true sonst (Normal-Zustand: Schutz sollte laufen) // - true sonst (Normal-Zustand: Schutz sollte laufen)
const protectionShouldBeActive = !active && profile?.protectionDisabledAt === null; const protectionShouldBeActive =
!active && profile?.protectionDisabledAt === null && !cooldownJustResolved;
return { return {
success: true, success: true,