summaryrefslogtreecommitdiffstats
path: root/docs/captcha.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/captcha.md')
-rw-r--r--docs/captcha.md110
1 files changed, 71 insertions, 39 deletions
diff --git a/docs/captcha.md b/docs/captcha.md
index c908a5c..84dadc4 100644
--- a/docs/captcha.md
+++ b/docs/captcha.md
@@ -12,7 +12,9 @@ reCAPTCHA v2 with the "I'm not a robot" checkbox. Reasons:
- Binary pass/fail — no score threshold to tune or monitor.
- The user is already filling a form; one checkbox is negligible friction.
-- Works in both the web SPA and the Electron client (both run Chromium).
+- Works in the web SPA. It works in the Electron client too, but not for the
+ reason "both run Chromium" — reCAPTCHA validates the *domain*, not the
+ rendering engine, and the desktop client's is not the hub's. See §6.
- v3 (invisible, score-based) is an option later if the checkbox proves annoying;
the server-side verification call is identical, only the client widget differs.
@@ -20,6 +22,9 @@ reCAPTCHA v2 with the "I'm not a robot" checkbox. Reasons:
`https://www.google.com/recaptcha/admin`. Register the hub's domain(s) —
`meshbay.org` and `localhost` for development. This produces a **site key**
(public, embedded in HTML) and a **secret key** (server-only, in `hub.toml`).
+If the desktop client is in use, also turn *off* "Verify the origin of
+reCAPTCHA solutions" on that key and set `allowed_hosts` — §6 says why, and
+what is given up.
---
@@ -376,49 +381,69 @@ captcha.failed: "Captcha verification failed — please try again"
---
-## 6. Desktop client (Electron)
+## 6. Desktop client (Electron), and the domain problem
-The Electron client loads its UI from the package (`app://meshbay`), not from
-the hub. Its CSP forbids external scripts by design — the renderer executes no
-code that did not ship in the package. Loading Google's reCAPTCHA script
-(`https://www.google.com/recaptcha/api.js`) would be a **new category of
-trust**: third-party executable code in a renderer that currently runs none.
+**This section replaced two earlier designs, and both are worth naming because
+the reasoning that produced them is the trap.**
-Two approaches:
+The first said: open the CSP to Google's reCAPTCHA domains, or skip the captcha
+for native clients — and recommended skipping it, keyed on `auth_key` being
+present. That carve-out shipped and was a hole: *every* real client sends
+`auth_key`, the browser included (it is the password split), so the gate was
+off for everybody and a bot skipped it by including the field. It is gone;
+`users.py` gates on `captcha.enabled` alone, and says so at the call site.
-- **Open the CSP to Google's reCAPTCHA domains.** Add
- `https://www.google.com/recaptcha/` and `https://www.gstatic.com/recaptcha/`
- to `script-src`, `frame-src` and `connect-src`. Functional, but undermines
- the principle that the renderer runs only packaged code — the reCAPTCHA
- script is fetched live and changes without the operator's knowledge.
-- **Skip the captcha for native clients** (recommended). The Electron client
- already raises the bar against automated account creation: it requires
- installation, generates a device Ed25519 key pair stored in `safeStorage`,
- and authenticates to the hub via `POST /v1/users/auth` with a signed
- challenge. A bot automating that path must install and drive a full Electron
- app, which is a harder problem than filling a web form — and the reCAPTCHA
- exists to solve the web-form problem.
+The second is the sentence in §1 above: "works in the Electron client too, both
+run Chromium". The CSP was opened (`RECAPTCHA_SRC` in `main.js`, covering
+`script-src`, `img-src` and `frame-src`) and the widget does render. It renders
+**"ERROR for site owner: Invalid domain for site key"**.
-**How to skip server-side.** The web SPA sends `password` (legacy) in the
-registration body; the Electron client sends `auth_key` (PBKDF2-derived via
-`window.MeshBayKeys.registerUser`). The server requires `captcha_token` only
-when `auth_key` is absent — i.e. the web path. This is not a security
-boundary: a bot that derives `auth_key` itself bypasses the check, but it also
-proves it can run the PBKDF2 derivation, which is the same cost as solving the
-captcha. The real gate for native-path abuse is the rate limiter (`5/minute`)
-and the email verification step.
+**Why.** reCAPTCHA validates the hostname of the page the widget is rendered
+in, against the domain list on the site key. The desktop client's interface
+ships inside the package and is served from `app://meshbay` (`main.js`:
+`win.loadURL('app://meshbay/index.html')`). Not a preference — `file://`
+breaks ES modules and IndexedDB, and the hub must never become the document
+origin, which is enforced by the `will-navigate` handler. So the hostname
+Google sees is `meshbay`, it is not on the key's list, and it never can be:
+the check happens on Google's servers and no client-side configuration reaches
+it. Widening the CSP does not help, because the CSP was never what refused.
-```python
-# In register(), captcha gate adjusted:
-if _cfg and _cfg.captcha.enabled and not body.auth_key:
- if not body.captcha_token:
- raise HTTPException(400, "captcha_required")
- ...
+**What is done instead.** Turn *off* "Verify the origin of reCAPTCHA
+solutions" on the key, and check the origin on the hub, where it belongs:
+
+```toml
+[captcha]
+site_key = "6Le..."
+secret_key = "6Le..."
+allowed_hosts = ["meshbay.org", "localhost", "meshbay"]
```
-On the Electron side: `registerUser()` in `keyderive.js` does not send
-`captcha_token`, and the server does not ask for one. No CSP change, no
-Google script loaded, no new trust boundary.
+`verify_captcha` then refuses a solve whose reported hostname is not in that
+list. The hostname comes from `siteverify` — it is what Google *observed*, not
+something the caller asserts — so this is a real check and not a formality: the
+site key is public, and the thing turning the origin check off opens is a bot
+rendering the widget on a page of its own, which this refuses on the hostname
+it actually served from.
+
+Empty (the default) means "do not check", so a hub that never touched this
+setting keeps the behaviour it has, with reCAPTCHA doing the origin check
+itself. **The two settings go together**: turning the console check off without
+setting `allowed_hosts` leaves no origin check anywhere.
+
+**The last entry is the desktop client's own, and it is the weak one.** Any
+Electron application can claim the same scheme and host — `main.js` already
+records that `app://meshbay` is not a credential, which is why the hub's API is
+reachable from no web origin at all and every call leaves from the main
+process. So `meshbay` in that list is spoofable by someone who builds an
+equivalent application. What it still costs them is a per-token captcha solve
+inside a real Chromium, rather than a token farmed from any web page. That is
+the trade, stated plainly; a hub that does not ship the desktop client should
+leave the entry out.
+
+**Confirming the hostname.** `meshbay` is the host component of
+`app://meshbay`. If a solve is refused, `captcha.py` logs it at WARNING with
+the hostname spelled out and the allowed list beside it, which is how to read
+the value a given client actually reports rather than guess at it.
---
@@ -475,11 +500,15 @@ from Google — no npm package.
## 10. Deployment steps
1. Obtain reCAPTCHA v2 keys from Google (register `meshbay.org` + `localhost`).
+ For a deployment with the desktop client, also turn off "Verify the origin
+ of reCAPTCHA solutions" on the key — §6.
2. Add to `/etc/meshbay/hub.toml` on the production server:
```toml
[captcha]
- site_key = "6Le..."
- secret_key = "6Le..."
+ site_key = "6Le..."
+ secret_key = "6Le..."
+ # Required whenever the console's origin check is off, and only then.
+ allowed_hosts = ["meshbay.org", "localhost", "meshbay"]
```
3. Deploy the new hub code (`deploy-hub.sh` — runs `alembic upgrade head` +
restart; no migration needed for this change).
@@ -488,3 +517,6 @@ from Google — no npm package.
solving the captcha.
5. Verify reset: open `https://meshbay.org/#/reset`, confirm the checkbox
appears. Request a reset code, confirm email arrives only after solving it.
+6. Verify the desktop client: register from it and confirm the widget solves
+ rather than showing "Invalid domain for site key". A refusal logged as
+ `captcha solved on an unexpected host` names the hostname to add.