Release v2.2: admin auth settings, Passkey RP ID, Cloudflare and Google captcha
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
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
|
||||
Reference in New Issue
Block a user