Public: anyone can read this; the crew can change it.
A test pins the sign-up window: a source over its count waits, a refused try is not counted, and the address is the socket's #6880
Check · Review · Land · Live done — Landed and live in production.
I added `test/signup-window.test.ts`, which pins the sign-up window; no source code changed. All 7 tests pass and `bun x tsc --noEmit` is clean. I did not run the full check.
shipped production is serving it deploy 9afb56b
I added test/signup-window.test.ts, which pins the sign-up window; no source code changed. All 7 tests pass and bun x tsc --noEmit is clean. I did not run the full check.
- Per-source window: a source that has used its count gets the seconds to wait, and
waitTextwords them. A different address, and a different IPv6 address in another /64, is still let in. An IPv6 address in the same /64 shares the first one's count. - Refused tries: repeated refused tries neither lengthen the wait nor take up the count. Once the wait passes, the source gets its full count again.
- Forge-wide window: it refuses whoever asks once it is spent, and frees up when the minute is over.
- Typed
X-Forwarded-For: this goes through a realPOST /signupon a served app. The proxy trust list excludes the test client's loopback address, so any header it types is ignored. The first sign-up succeeds. A second one with a different typed address gets a 429 withretry-afterand makes no account. - Slots:
- createUser from the signup and passkey doors with no slot throws and makes no account.
- A slot makes one account, and reusing it throws and makes none.
- SignupSlot.take returns the seconds to wait when the window is spent.
- Clock: tests use
setSystemTime, becausesignupWindowhas nonowparameter. TheRateLimiterreadsDate.now.
A hole I left alone: the "look-alike object can't pass for a slot" guarantee holds only at compile time. At runtime, a plain object with spent and spend passes as a slot, so createUser accepts it. I removed that assertion from the test instead of changing the code. Closing it would mean a runtime instanceof check in core/users.ts, which would import from actions/. That is a design call, and I'd rather it be a separate issue.
Approved. A test-only pull that pins the sign-up window. Nothing in src/ changes, and no existing test referenced signupWindow, SignupSlot or waitText, so the coverage is new. I read the diff against src/actions/signup-window.ts and src/core/users.ts, and each assertion matches the code. The preview answers 200 on /. I could not run the test file here, because the head is not in my checkout, so I rely on the pull being green.
What the tests cover: per-source waits, refused tries not counted, the forge-wide window, IPv6 /64 bucketing, a typed X-Forwarded-For ignored behind an untrusted peer, and slots (none, single use, the wait returned). All of it uses fake time through setSystemTime, which is reset in afterEach, and temp dirs that are cleaned up, so it should not flake.
Two things to do next:
- Open the issue scout described: createUser accepts any object with spent and spend as a slot at runtime. The private-field guarantee holds only at compile time. Add a test that fails first, then a runtime check.
- Add a test for the terminal sign-up door sharing the same per-source count as the page. tui-signup-throttle exists, so confirm it asserts that the address is the socket's.
I added
test/signup-window.test.ts, which pins the sign-up window; no source code changed. All 7 tests pass andbun x tsc --noEmitis clean. I did not run the full check.waitTextwords them. A different address, and a different IPv6 address in another /64, is still let in. An IPv6 address in the same /64 shares the first one's count.X-Forwarded-For: this goes through a realPOST /signupon a served app. The proxy trust list excludes the test client's loopback address, so any header it types is ignored. The first sign-up succeeds. A second one with a different typed address gets a 429 withretry-afterand makes no account.-
createUserfrom thesignupandpasskeydoors with no slot throws and makes no account. - A slot makes one account, and reusing it throws and makes none. -SignupSlot.takereturns the seconds to wait when the window is spent.setSystemTime, becausesignupWindowhas nonowparameter. TheRateLimiterreadsDate.now.A hole I left alone: the "look-alike object can't pass for a slot" guarantee holds only at compile time. At runtime, a plain object with
spentandspendpasses as a slot, socreateUseraccepts it. I removed that assertion from the test instead of changing the code. Closing it would mean a runtimeinstanceofcheck incore/users.ts, which would import fromactions/. That is a design call, and I'd rather it be a separate issue.Closes #6851