74 lines
2.3 KiB
Python
74 lines
2.3 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
from dataclasses import dataclass
|
|
|
|
import requests
|
|
|
|
TURNSTILE_VERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify"
|
|
DEFAULT_TURNSTILE_TEST_SITE_KEY = "1x00000000000000000000AA"
|
|
DEFAULT_TURNSTILE_TEST_SECRET_KEY = "1x0000000000000000000000000000000AA"
|
|
ALWAYS_FAIL_TURNSTILE_TEST_SITE_KEY = "2x00000000000000000000AB"
|
|
ALWAYS_FAIL_TURNSTILE_TEST_SECRET_KEY = "2x0000000000000000000000000000000AA"
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TurnstileSettings:
|
|
site_key: str
|
|
secret_key: str
|
|
enabled: bool
|
|
uses_test_keys: bool
|
|
|
|
|
|
def _environment() -> str:
|
|
return os.getenv("APP_ENV", os.getenv("ENVIRONMENT", "development")).lower()
|
|
|
|
|
|
def load_turnstile_settings() -> TurnstileSettings:
|
|
site_key = os.getenv("TURNSTILE_SITE_KEY", "")
|
|
secret_key = os.getenv("TURNSTILE_SECRET_KEY", "")
|
|
enabled = os.getenv("TURNSTILE_ENABLED", "true").lower() not in {"0", "false", "no"}
|
|
env = _environment()
|
|
|
|
if not site_key or not secret_key:
|
|
if env in {"development", "test"}:
|
|
site_key = site_key or DEFAULT_TURNSTILE_TEST_SITE_KEY
|
|
secret_key = secret_key or DEFAULT_TURNSTILE_TEST_SECRET_KEY
|
|
else:
|
|
raise RuntimeError("Turnstile keys must be configured outside development/test environments")
|
|
|
|
uses_test_keys = site_key == DEFAULT_TURNSTILE_TEST_SITE_KEY and secret_key == DEFAULT_TURNSTILE_TEST_SECRET_KEY
|
|
return TurnstileSettings(
|
|
site_key=site_key,
|
|
secret_key=secret_key,
|
|
enabled=enabled,
|
|
uses_test_keys=uses_test_keys,
|
|
)
|
|
|
|
|
|
def verify_turnstile_token(token: str, remote_ip: str | None = None) -> bool:
|
|
settings = load_turnstile_settings()
|
|
if not settings.enabled:
|
|
return True
|
|
if not token.strip():
|
|
return False
|
|
try:
|
|
response = requests.post(
|
|
TURNSTILE_VERIFY_URL,
|
|
data={
|
|
"secret": settings.secret_key,
|
|
"response": token,
|
|
"remoteip": remote_ip or "",
|
|
},
|
|
timeout=10,
|
|
)
|
|
response.raise_for_status()
|
|
payload = response.json()
|
|
except (requests.RequestException, ValueError) as exc:
|
|
logger.warning("Turnstile verification failed: %s", exc)
|
|
return False
|
|
return bool(payload.get("success"))
|