diff --git a/README.md b/README.md index 544fa056..ab3a4ffd 100644 --- a/README.md +++ b/README.md @@ -851,6 +851,78 @@ Open **DevOps → Anti-DDoS protection** --- +## Captcha (Cloudflare Turnstile) + +The **register**, **login**, **forgot-password** and **resend-verification** +forms can be protected by a captcha. **Cloudflare Turnstile** is the supported +provider and is free; reCAPTCHA and hCaptcha are also wired up. If you only +read one thing, read the precedence table below. + +### Where the captcha is configured (and who wins) + +There are two places, and the **admin panel always wins**: + +1. **Admin panel** — *Admin → CMS Settings → Captcha* (stored in the database). +2. **Environment variables** — used **only** as a fallback when the admin panel + has no value. + +Resolution order, highest first: + +| Admin panel `captcha_provider` | Env `CAPTCHA_PROVIDER` | What happens | +| --- | --- | --- | +| `turnstile` | anything | **Turnstile** (panel wins) | +| `none` (set explicitly) | `turnstile` | **Off** — an explicit panel value wins | +| *empty / never set* | `turnstile` | **Turnstile** (env fallback) | +| *empty / never set* | *empty / never set* | **Off** | + +In short: **enable it in the admin panel and the env vars are ignored; leave the +panel empty and the env vars take over.** An explicit `none` in the panel turns +the captcha **off** even when `CAPTCHA_PROVIDER=turnstile` is set. + +### Option A — configure with environment variables (per deploy) + +1. Create a Turnstile widget in the + [Cloudflare dashboard](https://dash.cloudflare.com/?to=/:account/turnstile): + **Add widget**, name it, and add the hostnames your hotel is served on + (e.g. `epicnabbo.nl`, `www.epicnabbo.nl`). *Managed* mode is fine. Copy the + **Site Key** and the **Secret Key**. +2. Put them in your real `.env` (never commit this file): + + ```dotenv + CAPTCHA_PROVIDER=turnstile + TURNSTILE_SITE_KEY= # public — safe to expose + TURNSTILE_SECRET_KEY= # SECRET — server-side only + ``` + + - The **site key** is **public**: it is rendered into the page for the + browser. + - The **secret key** is **secret**: only the server uses it to call + Cloudflare's `siteverify` endpoint. **Never commit it** — keep it in the + host `.env`, or a **masked CI/CD variable** if you deploy from CI. +3. Restart the container so the new env is picked up. + +### Option B — configure in the admin panel + +**Admin → CMS Settings → Captcha**: pick the provider and paste the site key and +secret. This overrides the env vars and takes effect **without a restart**. + +### Verify + +- Open `/register`. When active, the Turnstile widget appears above the submit + button. +- The e2e suite can assert the widget contract: + `E2E_EXPECT_CAPTCHA=turnstile pnpm test:e2e` (spec: `e2e/register.spec.ts`). + +### Notes + +- **Fail-closed**: if the provider is Turnstile but no secret key is available, + verification fails and the form is blocked — it never silently lets bots in. +- The Content-Security-Policy already allows `https://challenges.cloudflare.com` + (`src/lib/csp.ts`), so no header changes are needed. +- Turnstile does **not** require the Cloudflare proxy (orange cloud); it works + independently of the anti-DDoS layer above. + +--- ## Production Deployment (blue/green) @@ -1181,6 +1253,13 @@ pnpm jobs:worker # or: npm run jobs:worker / yarn jobs:worker Optional OpenAI-powered moderation for comments and guestbook posts. Set `OPENAI_API_KEY`. +### Captcha — Cloudflare Turnstile + +Protect the register, login, forgot-password and resend-verification forms. +Set `CAPTCHA_PROVIDER=turnstile` plus the Turnstile keys in `.env`, or configure +it in **Admin → CMS Settings**. Full setup, precedence and verification: +[Captcha (Cloudflare Turnstile)](#captcha-cloudflare-turnstile). + ### Byparr (Cloudflare Bypass) Some clone sources are protected by Cloudflare. Byparr (a FlareSolverr successor) solves these challenges automatically.