diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-01 17:49:13 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-01 17:49:13 +0200 |
| commit | 7d8c774e250b134222374f45726d5426714475de (patch) | |
| tree | d5cf7e7df3131dc51c5c46cc0e8c7ffb4aed0240 /packages/meshbay-hub/tests/test_register_captcha.py | |
| parent | 99eb93a00269fbfafba7536cf1613b3d4c18c3ce (diff) | |
| download | meshbay-7d8c774e250b134222374f45726d5426714475de.tar.gz | |
fix(hub): enforce registration captcha for every client
The server only checked the captcha when auth_key was absent — but
every real client (browser included, via the password split) sends
auth_key, so the check was off for everyone, and a bot skipped it by
including the field. The Register form still made humans solve a
widget whose token was never transmitted.
Gate is now unconditional on captcha.enabled. The web client
(registerUser in keyderive.js) forwards captcha.token; RegisterPage
resets the (single-use) token on a failed attempt. The desktop client
shares this UI source and is Chromium, so it renders the same widget
(see the paired meshbay-client commit for the CSP change that allows
it).
Tests: test_register_captcha.py.
Third security review, finding M1 (Option A).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011pG75yGK3NthNfyjH74omG
Diffstat (limited to 'packages/meshbay-hub/tests/test_register_captcha.py')
| -rw-r--r-- | packages/meshbay-hub/tests/test_register_captcha.py | 58 |
1 files changed, 58 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_register_captcha.py b/packages/meshbay-hub/tests/test_register_captcha.py new file mode 100644 index 0000000..befc1e2 --- /dev/null +++ b/packages/meshbay-hub/tests/test_register_captcha.py @@ -0,0 +1,58 @@ +"""Registration CAPTCHA is enforced for every fresh account when configured. + +The gate used to be skipped whenever the request carried an `auth_key` — which +every real client sends (the password split) — so it protected nobody and a bot +skipped it by including the field. It now runs on `captcha.enabled` alone; the +desktop client is Chromium and renders the same widget. +""" + +import pytest + + +@pytest.fixture +def captcha_on(client, monkeypatch): + """Turn on a fake captcha: any config with both keys is `enabled`, and + verification succeeds only for the token 'good-token'.""" + from meshbay_hub.api.users import _cfg + monkeypatch.setattr(_cfg.captcha, "site_key", "test-site") + monkeypatch.setattr(_cfg.captcha, "secret_key", "test-secret") + + async def fake_verify(secret, token, remote_ip=None): + return token == "good-token" + + monkeypatch.setattr("meshbay_hub.captcha.verify_captcha", fake_verify) + + +def _body(**over): + b = {"username": "newbie", "email": "newbie@t.com", "auth_key": "a" * 44} + b.update(over) + return b + + +@pytest.mark.asyncio +async def test_missing_captcha_rejected_even_with_auth_key(client, captcha_on): + r = await client.post("/v1/users/register", json=_body()) + assert r.status_code == 400 + assert r.json()["detail"] == "captcha_required" + + +@pytest.mark.asyncio +async def test_bad_captcha_rejected(client, captcha_on): + r = await client.post("/v1/users/register", + json=_body(captcha_token="wrong")) + assert r.status_code == 400 + assert r.json()["detail"] == "captcha_failed" + + +@pytest.mark.asyncio +async def test_good_captcha_accepted(client, captcha_on): + r = await client.post("/v1/users/register", + json=_body(captcha_token="good-token")) + assert r.status_code == 201 + + +@pytest.mark.asyncio +async def test_no_captcha_configured_still_registers(client): + # Default test config has no captcha keys — registration proceeds without one. + r = await client.post("/v1/users/register", json=_body()) + assert r.status_code == 201 |