0584ebdc74
Co-authored-by: Cursor <cursoragent@cursor.com>
124 lines
4.0 KiB
Python
124 lines
4.0 KiB
Python
import requests
|
|
|
|
from app.settings_service import get_settings
|
|
|
|
CAPTCHA_PROVIDERS = ("none", "turnstile", "recaptcha_v2", "recaptcha_v3")
|
|
|
|
ENDPOINT_PAGE_MAP = {
|
|
"auth.login": "login",
|
|
"auth.register": "register",
|
|
"auth.forgot_password": "forgot_password",
|
|
}
|
|
|
|
|
|
def get_captcha_config(page):
|
|
settings = get_settings()
|
|
provider = (settings.captcha_provider or "none").strip()
|
|
if provider not in CAPTCHA_PROVIDERS or provider == "none":
|
|
return None
|
|
|
|
page_flags = {
|
|
"login": settings.captcha_on_login,
|
|
"register": settings.captcha_on_register,
|
|
"forgot_password": settings.captcha_on_forgot_password,
|
|
}
|
|
if not page_flags.get(page):
|
|
return None
|
|
|
|
config = {"provider": provider, "page": page}
|
|
if provider == "turnstile":
|
|
config["site_key"] = (settings.turnstile_site_key or "").strip()
|
|
elif provider == "recaptcha_v2":
|
|
config["site_key"] = (settings.recaptcha_v2_site_key or "").strip()
|
|
elif provider == "recaptcha_v3":
|
|
config["site_key"] = (settings.recaptcha_v3_site_key or "").strip()
|
|
config["action"] = page
|
|
|
|
if not config.get("site_key"):
|
|
return None
|
|
return config
|
|
|
|
|
|
def get_captcha_config_for_request(endpoint):
|
|
page = ENDPOINT_PAGE_MAP.get(endpoint)
|
|
if not page:
|
|
return None
|
|
return get_captcha_config(page)
|
|
|
|
|
|
def verify_captcha(request, page):
|
|
config = get_captcha_config(page)
|
|
if not config:
|
|
return True, None
|
|
|
|
settings = get_settings()
|
|
provider = config["provider"]
|
|
|
|
if provider == "turnstile":
|
|
token = (request.form.get("cf-turnstile-response") or "").strip()
|
|
secret = (settings.turnstile_secret_key or "").strip()
|
|
if not token:
|
|
return False, "Подтвердите captcha"
|
|
if not secret:
|
|
return False, "Captcha не настроена (нет secret key)"
|
|
return _verify_turnstile(token, secret)
|
|
|
|
token = (request.form.get("g-recaptcha-response") or "").strip()
|
|
if provider == "recaptcha_v2":
|
|
secret = (settings.recaptcha_v2_secret_key or "").strip()
|
|
if not token:
|
|
return False, "Подтвердите captcha"
|
|
if not secret:
|
|
return False, "Captcha не настроена (нет secret key)"
|
|
return _verify_recaptcha(token, secret)
|
|
|
|
secret = (settings.recaptcha_v3_secret_key or "").strip()
|
|
if not token:
|
|
return False, "Подтвердите captcha"
|
|
if not secret:
|
|
return False, "Captcha не настроена (нет secret key)"
|
|
ok, msg, score = _verify_recaptcha_with_score(token, secret)
|
|
if not ok:
|
|
return False, msg
|
|
min_score = settings.recaptcha_v3_min_score if settings.recaptcha_v3_min_score is not None else 0.5
|
|
if score < min_score:
|
|
return False, "Подозрительная активность, попробуйте снова"
|
|
return True, None
|
|
|
|
|
|
def _verify_turnstile(token, secret):
|
|
try:
|
|
response = requests.post(
|
|
"https://challenges.cloudflare.com/turnstile/v0/siteverify",
|
|
data={"secret": secret, "response": token},
|
|
timeout=10,
|
|
)
|
|
data = response.json()
|
|
except requests.RequestException:
|
|
return False, "Не удалось проверить captcha"
|
|
|
|
if data.get("success"):
|
|
return True, None
|
|
return False, "Captcha не пройдена"
|
|
|
|
|
|
def _verify_recaptcha(token, secret):
|
|
ok, msg, _score = _verify_recaptcha_with_score(token, secret)
|
|
return ok, msg
|
|
|
|
|
|
def _verify_recaptcha_with_score(token, secret):
|
|
try:
|
|
response = requests.post(
|
|
"https://www.google.com/recaptcha/api/siteverify",
|
|
data={"secret": secret, "response": token},
|
|
timeout=10,
|
|
)
|
|
data = response.json()
|
|
except requests.RequestException:
|
|
return False, "Не удалось проверить captcha", 0.0
|
|
|
|
if data.get("success"):
|
|
return True, None, float(data.get("score") or 1.0)
|
|
return False, "Captcha не пройдена", 0.0
|