timescale.info
Sign inSign up

How OpenChronology was built

Every merged pull request of openchronology/timeline-app, its first week of commits and its releases; and before them the 2020 PureScript editor, from snapshots of its deleted repositories in the Wayback Machine. Written to this server through the HTTP API.

327 moments · 2020-03-09T00:15:08Z CE – 2026-11-02T20:11:21Z CE · by @athan · @athan

#62 · Spam protection: human checks with self-hosted proof of work or Turnstile

  • timeline-app
  • pull request

Fixes #48. ## When a check is asked for | Action | When | |---|---| | Registration, forgot password, email enrolment | Always (they send email to a typed address) | | Password sign-in | After 3 failures in 15 min for the address or the account, instead of locking out | | New pull request, pull-request comment | Account younger than `CHALLENGE_TRUST_DAYS` (7) that isn't the timeline's owner or a member; admins never | | Plugin publishing | Always, except admins | ## How it works - The server refuses with **428** and a `challenge`. Both client helpers (`src/transport.ts` for the editor and desktop, `platform/lib/api.js` for the website) answer it in a small dialog and retry with `X-Challenge`. No form needed its own changes. The desktop proxy forwards the header. - **`pow` (default):** self-hosted proof of work in the ALTCHA style. The browser finds `n` with `sha256(salt + n) = challenge`. The salt carries the purpose and expiry and is HMAC-signed with a key derived from `AUTH_ENCRYPTION_KEY`. Answers last 10 minutes, and each is single-use through the new `oc_challenge_uses` table. The default difficulty of 150,000 is about 0.5 s on a desktop (measured at 100k hashes per 0.6–1.1 s in headless Chromium and Firefox) and a few seconds on phones. The solver yields every ~4k hashes so the dialog stays responsive and Cancel works. - **`turnstile`:** Cloudflare Turnstile with `TURNSTILE_SITE_KEY` and `TURNSTILE_SECRET_KEY`. The server verifies with siteverify (10 s timeout) and checks `success`, `action` (bound to the purpose) and `hostname` (the `APP_ORIGIN` host). The CSP allows `https://challenges.cloudflare.com` scripts and frames only in this mode, both on the website and in the editor frame. Desktop and API-key sessions get proof of work instead, since they can't show the widget. - **`off`** disables checks. Proof of work raises the cost of bulk scripting, but native code can still solve it quickly. The docs say so, and the existing rate limits stay in place. Turnstile is the stronger option. ## Notes - `createRequestHandler` defaults to no checks, so existing service tests are unchanged. The platform (`server/platform.mjs`) reads the env and defaults to `pow`. - Behind Next.js without `TRUST_PROXY=1`, all anonymous visitors share one address. After three failures, everyone gets a check rather than a lockout. This is documented. - Config is in `.env.example`, `compose.yml` and `docs/authentication.md` (new "Human verification" section). `docs/plugins.md` is updated too. ## Tests - `test/challenge.test.mjs`: - proof of work: signature, purpose binding, replay, foreign key, expiry and malformed answers; - Turnstile: action, hostname and outage (503); - desktop and API sessions get proof of work; - settings validation. - `test/challenge-postgres.mjs` (added to CI): - registration and forgot-password through HTTP; - sign-in after three failures, including from another address; - contributions: a new stranger is asked; owner, member, veteran and admin are not; - plugin publishing. - It deletes its accounts afterwards so later suites in the shared database aren't affected. - `test/challenge-browser.mjs`: the editor's real sign-in form against a mocked server that uses the real verifier. It covers proof of work solved in the dialog with a retry, Cancel refusing, and a stubbed Turnstile widget. - Unit, browser and offline suites pass in Chromium and Firefox. The PostgreSQL account, admin, platform and `postgres.mjs` suites, the Next platform test and the desktop crate tests all pass.

Sources

Open #62 · Spam protection: human checks with self-hosted proof of work or Turnstile in the timeline