Schutz gegen kursierende Partner-Codes (Rene: 'Bonus-Codes kursieren gerne das Internet')

1. QR-URL verrät den Code nicht mehr: /q/{token} → /?qr=TOKEN (vorher stand
   der tippbare Code in der Adresszeile jedes Scanners). Registrierung löst
   den Code server-seitig aus dem Token auf (auch ohne ref_code).
2. Notbremse: partner_codes.active — Admin kann Codes pausieren (Einlösung
   gesperrt, Info-Endpoint 404, Historie/QR-Kontingente bleiben) und
   reaktivieren. UI: ⏸/▶-Toggle + pausiert-Badge in der Codes-Tabelle.
3. max_uses im Anlege-Formular standardmäßig 50 statt unbegrenzt.

Tests: QR-only-Registrierung, Pause→keine Einlösung→Reaktivierung,
Redirect ohne Klartext-Code. Suite: 54 passed.
This commit is contained in:
rene 2026-06-07 19:35:31 +02:00
parent 21bcc6b962
commit 2927ae2672
11 changed files with 136 additions and 39 deletions

View file

@ -2165,12 +2165,7 @@ async def partner_qr_scan(token: str):
token = token.strip()
with _db() as conn:
row = conn.execute(
"""SELECT q.token, pc.code
FROM partner_qr_codes q
JOIN partner_qr_batches b ON b.id = q.batch_id
JOIN partner_codes pc ON pc.id = b.partner_code_id
WHERE q.token = ?""",
(token,)
"SELECT token FROM partner_qr_codes WHERE token = ?", (token,)
).fetchone()
if not row:
return _Redirect("/", status_code=302)
@ -2182,8 +2177,10 @@ async def partner_qr_scan(token: str):
WHERE token = ?""",
(token,)
)
# ?ref= nutzt den bestehenden Partner-Code-Flow, ?qr= ergänzt die Einzelcode-Zuordnung
return _Redirect(f"/?ref={row['code']}&qr={row['token']}", status_code=302)
# Bewusst NUR der Token in der URL — der tippbare Partner-Code bleibt verborgen
# (sonst könnte jeder Sticker-Scanner den Code ablesen und beliebig weitergeben).
# Die Registrierung löst den Code server-seitig aus dem Token auf.
return _Redirect(f"/?qr={row['token']}", status_code=302)
# ------------------------------------------------------------------