aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/captcha.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-01 11:06:47 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-01 11:06:47 +0200
commitb6c15f35d570d4f54901b811654991847502ca82 (patch)
treea3d0a42c3b48aa7ac618094cedc59b5b3f329376 /packages/meshbay-hub/src/meshbay_hub/captcha.py
parent1c8eb6577e36e1e4a150afd0cdda8d283172385e (diff)
downloadmeshbay-b6c15f35d570d4f54901b811654991847502ca82.tar.gz
feat(hub): reCAPTCHA v2 on Register and Password Reset pages
Server-side verification module, CaptchaConfig in hub.toml, captcha_site_key exposed via /v1/hub/info, useCaptcha() hook in the SPA with stable DOM rendering (strength bar always present to avoid Preact re-ordering the captcha widget). Native clients (auth_key path) skip captcha. All 10 locales updated. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/captcha.py')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/captcha.py28
1 files changed, 28 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/captcha.py b/packages/meshbay-hub/src/meshbay_hub/captcha.py
new file mode 100644
index 0000000..faf7907
--- /dev/null
+++ b/packages/meshbay-hub/src/meshbay_hub/captcha.py
@@ -0,0 +1,28 @@
+"""reCAPTCHA v2 server-side verification."""
+
+import logging
+
+import httpx
+
+log = logging.getLogger(__name__)
+
+VERIFY_URL = "https://www.google.com/recaptcha/api/siteverify"
+
+
+async def verify_captcha(
+ secret_key: str, token: str, remote_ip: str | None = None,
+) -> bool:
+ payload: dict[str, str] = {"secret": secret_key, "response": token}
+ if remote_ip:
+ payload["remoteip"] = remote_ip
+ try:
+ async with httpx.AsyncClient(timeout=5) as client:
+ resp = await client.post(VERIFY_URL, data=payload)
+ resp.raise_for_status()
+ result = resp.json()
+ if not result.get("success"):
+ log.info("captcha rejected: %s", result.get("error-codes", []))
+ return result.get("success", False)
+ except Exception:
+ log.exception("captcha verification request failed")
+ return False