From b6c15f35d570d4f54901b811654991847502ca82 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Tue, 1 Sep 2026 11:06:47 +0200 Subject: 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 --- packages/meshbay-hub/src/meshbay_hub/config.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'packages/meshbay-hub/src/meshbay_hub/config.py') diff --git a/packages/meshbay-hub/src/meshbay_hub/config.py b/packages/meshbay-hub/src/meshbay_hub/config.py index b508e45..48d5a6e 100644 --- a/packages/meshbay-hub/src/meshbay_hub/config.py +++ b/packages/meshbay-hub/src/meshbay_hub/config.py @@ -61,12 +61,23 @@ class JWTConfig: refresh_token_ttl: int = 86400 * 30 # 30 days +@dataclass +class CaptchaConfig: + site_key: str = "" + secret_key: str = "" + + @property + def enabled(self) -> bool: + return bool(self.site_key and self.secret_key) + + @dataclass class HubConfig: db: DatabaseConfig = field(default_factory=DatabaseConfig) server: ServerConfig = field(default_factory=ServerConfig) identity: HubIdentityConfig = field(default_factory=HubIdentityConfig) jwt: JWTConfig = field(default_factory=JWTConfig) + captcha: CaptchaConfig = field(default_factory=CaptchaConfig) def load_config(path: Path | None = None) -> HubConfig: @@ -92,6 +103,9 @@ def load_config(path: Path | None = None) -> HubConfig: if jwt := raw.get("jwt", {}): cfg.jwt.access_token_ttl = jwt.get("access_token_ttl", cfg.jwt.access_token_ttl) cfg.jwt.refresh_token_ttl = jwt.get("refresh_token_ttl", cfg.jwt.refresh_token_ttl) + if cap := raw.get("captcha", {}): + cfg.captcha.site_key = cap.get("site_key", cfg.captcha.site_key) + cfg.captcha.secret_key = cap.get("secret_key", cfg.captcha.secret_key) break # Env var overrides @@ -107,5 +121,9 @@ def load_config(path: Path | None = None) -> HubConfig: cfg.identity.private_key_path = Path(kp).expanduser() if admin_users := os.environ.get("MESHBAY_ADMIN_USERS"): cfg.identity.admin_usernames = [u.strip() for u in admin_users.split(",") if u.strip()] + if captcha_site := os.environ.get("MESHBAY_CAPTCHA_SITE_KEY"): + cfg.captcha.site_key = captcha_site + if captcha_secret := os.environ.get("MESHBAY_CAPTCHA_SECRET_KEY"): + cfg.captcha.secret_key = captcha_secret return cfg -- cgit v1.2.3