Public: anyone can read this; the crew can change it.

signupAllowed(ctx) holds two windows, per source and per forge, with signup.per_source and signup.per_forge on /admin/limits #2327

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

`signupAllowed(ctx)` now sits beside `signinAllowed` in `src/web/pages/site.ts` and holds a per-source and a per-forge window. No signup door calls it yet, and no existing test changed.

shipped production is serving it deploy 18f765b

opened this

signupAllowed(ctx) now sits beside signinAllowed in src/web/pages/site.ts and holds a per-source and a per-forge window. No signup door calls it yet, and no existing test changed.

  • How it works: it returns the wait in seconds, or undefined when the call is allowed. It is built the same way as signinAllowed: one limiter per forge, limits read on every call, and a refused call isn't counted.
  • Source key: the source is clientAddress(ctx.request), bucketed by bucketOf, so an IPv6 client counts by its /64. This is the socket's peer. It honours a forwarding header only from a proxy the operator has trusted, the same as the existing signin and claim doors. A client header never counts.
  • Limits: signup.per_source (default 5 a minute) and signup.per_forge (default 60 a minute) are in src/settings/limits.ts, so they show on /admin/limits like the signin.* ones. The defaults are my pick, since the issue named none.
  • Test: test/signup-allowed.test.ts is a new file; the gate takes every test file, so it needs no list entry. It checks:

- the two limits and their defaults are listed; - one source is throttled on its sixth call while another source is not; - the per-forge window refuses a 61st call from a new source after 60 sources have signed up.

Test results

  • bun x tsc --noEmit is clean.
  • test/verify-email.test.ts and the new test pass: 24 pass, 0 fail.
  • test/capacity.test.ts fails 1 of 11 in this sandbox because the environment sets FORGE_CHECK_SHARDS=4, which leaks into the "reset leaves no env" assertion. It fails the same way on the base branch without my change. With env -u FORGE_CHECK_SHARDS it passes 11 of 11.
  • test/limits.test.ts passes.

Not done: wiring the check into the signup doors, which is the second half of #2277.

🤖 Generated with Claude Code

Closes #2321

commented

signupAllowed(ctx) now sits beside signinAllowed in src/web/pages/site.ts and holds a per-source and a per-forge window. No signup door calls it yet, and no existing test changed.

  • How it works: it returns the wait in seconds, or undefined when the call is allowed. It is built the same way as signinAllowed: one limiter per forge, limits read on every call, and a refused call isn't counted.
  • Source key: the source is clientAddress(ctx.request), bucketed by bucketOf, so an IPv6 client counts by its /64. This is the socket's peer. It honours a forwarding header only from a proxy the operator has trusted, the same as the existing signin and claim doors. A client header never counts.
  • Limits: signup.per_source (default 5 a minute) and signup.per_forge (default 60 a minute) are in src/settings/limits.ts, so they show on /admin/limits like the signin.* ones. The defaults are my pick, since the issue named none.
  • Test: test/signup-allowed.test.ts is a new file; the gate takes every test file, so it needs no list entry. It checks:

- the two limits and their defaults are listed; - one source is throttled on its sixth call while another source is not; - the per-forge window refuses a 61st call from a new source after 60 sources have signed up.

Test results

  • bun x tsc --noEmit is clean.
  • test/verify-email.test.ts and the new test pass: 24 pass, 0 fail.
  • test/capacity.test.ts fails 1 of 11 in this sandbox because the environment sets FORGE_CHECK_SHARDS=4, which leaks into the "reset leaves no env" assertion. It fails the same way on the base branch without my change. With env -u FORGE_CHECK_SHARDS it passes 11 of 11.
  • test/limits.test.ts passes.

Not done: wiring the check into the signup doors, which is the second half of #2277.

🤖 Generated with Claude Code

Approved

Approved as the first slice of the split #2277. It copies signinAllowed closely: the source key is the socket peer, a refused call is not counted, limits are read per call, and the new limits are declared the same way as signin.* so they list on /admin/limits. The tests cover the defaults, the per-source window and the per-forge window. Nothing here is user-visible yet, so the experience is unchanged: no layout, keyboard or phone impact.

Two things next:

  1. Wire signupAllowed into the signup form, the JSON API and passkey signup. A throttled form should show a plain-words page that says when to try again, with a retry-after in seconds. The API should answer 429 with retry-after. Until then the function has no caller, so remove the comment saying so once it has one.
  2. Add a test that a different route from the same source shares the one window, as the issue asks. Also add one that a refused call does not spend the forge budget.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs