Name, Seitentitel, Dienste (playwish-web, playwish-controller), Dienst-Benutzer (playwish), Pfade (/opt/playwish, /usr/local/src/playwish), Vorlagen und Repository-Adresse. „Audiofy“ war bereits von mehreren Produkten belegt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
59 lines
2.3 KiB
Python
59 lines
2.3 KiB
Python
"""Playwish-Web – Cloudflare Turnstile: Bot-Schutz für Admin-Login und Wünsche.
|
||
|
||
Kein Domain-Umzug nötig: Der Browser holt sich bei Cloudflare ein Token, der
|
||
Server prüft es per siteverify. Ohne TURNSTILE_SITE_KEY/TURNSTILE_SECRET_KEY in
|
||
der .env ist der Schutz aus (z. B. in Tests).
|
||
|
||
Ist Cloudflare nicht erreichbar, bleibt der Admin-Login gesperrt (sicher),
|
||
Wünsche gehen dagegen durch (die Party soll nicht stillstehen) – mit Logeintrag.
|
||
"""
|
||
|
||
import logging
|
||
from urllib.parse import urlsplit
|
||
|
||
import httpx
|
||
from fastapi import HTTPException, Request
|
||
|
||
from .config import konfig
|
||
|
||
log = logging.getLogger("playwish.turnstile")
|
||
|
||
VERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify"
|
||
|
||
|
||
def aktiv() -> bool:
|
||
k = konfig()
|
||
return bool(k.turnstile_site_key and k.turnstile_secret)
|
||
|
||
|
||
def _siteverify(daten: dict) -> dict:
|
||
r = httpx.post(VERIFY_URL, data=daten, timeout=8)
|
||
r.raise_for_status()
|
||
return r.json()
|
||
|
||
|
||
def pruefen(token: str | None, request: Request, aktion: str, bei_ausfall_erlauben: bool) -> None:
|
||
"""Wirft HTTPException, wenn das Turnstile-Token fehlt oder ungültig ist."""
|
||
if not aktiv():
|
||
return
|
||
if not token:
|
||
raise HTTPException(400, "Bitte warte kurz, bis die Sicherheitsprüfung abgeschlossen ist.")
|
||
daten = {"secret": konfig().turnstile_secret, "response": token}
|
||
if request.client:
|
||
daten["remoteip"] = request.client.host
|
||
try:
|
||
ergebnis = _siteverify(daten)
|
||
except (httpx.HTTPError, ValueError) as e:
|
||
if bei_ausfall_erlauben:
|
||
log.warning("Turnstile nicht erreichbar (%s) – %s ausnahmsweise ohne Prüfung erlaubt",
|
||
type(e).__name__, aktion)
|
||
return
|
||
log.error("Turnstile nicht erreichbar (%s) – %s abgelehnt", type(e).__name__, aktion)
|
||
raise HTTPException(503, "Die Sicherheitsprüfung ist gerade nicht erreichbar – bitte gleich nochmal versuchen.")
|
||
erwarteter_host = urlsplit(konfig().public_url).hostname
|
||
if (not ergebnis.get("success")
|
||
or (erwarteter_host and ergebnis.get("hostname") not in (None, erwarteter_host))
|
||
or (ergebnis.get("action") and ergebnis["action"] != aktion)):
|
||
log.info("Turnstile abgelehnt (%s): %s", aktion, ergebnis.get("error-codes"))
|
||
raise HTTPException(403, "Die Sicherheitsprüfung ist fehlgeschlagen – bitte nochmal versuchen.")
|