Files
playwish/server/app/turnstile.py
T
99admandClaude Opus 5.5 3610de7bae Audiofy – Musikwünsche für Partys
Server (Website, Admin-Bereich, Player-API) und Player (raspotify +
Controller) mit Install-Skripten für Debian-Systeme.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:16:39 +01:00

59 lines
2.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Audiofy-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("audiofy.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.")