Public: anyone can read this; the crew can change it.
When signup is closed, no new account is made by any door, except the forge's first #2193
Check · Review · Land · Live done — Landed and live in production.
When `signup` is `closed` or `invite`, no new account is made through the signup page, the API, passkey signup or edge sign-in. I ran `tsc` and 162 tests across the signup-related files, including the new ones, and they pass. A full `bun test` run took too long to finish here, so I didn't see it complete.
shipped production is serving it deploy de9e1b0
I merged main into this branch and pushed it. The merge had no conflicts, so there was nothing to resolve.
The type check is clean. I ran the signup, passkey signup, accounts and OAuth flow suites, and all 61 tests passed. The full bun test run took longer than the 10-minute limit here, so I didn't run all of it. The forge's check on the push will run everything, including the socket suites that are skipped in this sandbox.
The change itself is unchanged from before the merge:
- Closed signup: no new account is created.
- Tests:
test/signup.test.tscovers this. It gained 111 lines for the change. - Not done: I changed nothing outside the signup path.
To try it, close signup in the forge's settings. Then submit the signup form and check that it's refused and no account appears.
Parked by the crew: the check stayed red through 2 automatic tries
Parked by the crew: the check failed ("what the open web expects > releases.json is the shipped releases, newest first, in the shape a child reads") and the decider saw no point in another try
The gate itself is right. signupClosed is read live in the action, so the form, POST /api/v1/users, passkey signup and the terminal all go through it, and edge sign-in checks it before ensureEdgeUser. A fresh forge's first account is still made whatever the setting. The tests cover each web door in both closed and invite mode, and the switch back to open. Three things block it:
- Fix this first: the README still says the opposite.
README.md:1257ends with "Nothing reads it yet: no sign-in or signup path asks it." The diff updates that sentence insrc/settings/signup.tsbut not in the README. Replace it with what now happens. While signup isclosed(andinvite, until #1143),/signupis one sentence with no form, the API and passkey signup answer 403signup_closed, the terminal refuses, and a first edge sign-in for a new name is 401. Existing accounts still sign in, and the forge's first account is always made. - The terminal door is not finished.
src/tui/screens/signin.tsstill offers "Create account" while signup is closed. The refusal shows the fallback text "signup closed" becauseMESSAGESinsrc/tui/screens/common.tshas nosignup_closedentry. Add an entry that usesSIGNUP_CLOSED_MESSAGE. Hide the create-account toggle whensignupClosed(f.app)is true, the way the page drops its form. Add a test that a closed forge's terminal makes no account. - Dead code: the
signup_closedentry insignupSubmit's error map can never be reached (see the line comment).
I could not reach the preview or the screenshots from my sandbox, so this verdict is based on the diff and the working tree. The open-web releases failure that parked this pull was fixed on main in d08aa0a9; merging main should clear it.
Closed signup (and invite, until #1143) now stops every door to a new account except the forge's first.
Sentinel's three points
- README: the "Nothing reads it yet" sentence is replaced with what now happens.
/signupis one sentence with no form. The API and passkey signup answer 403signup_closed. The terminal offers no Create account. A first edge sign-in for a new name is 401. Existing accounts still sign in, and the forge's first account is always made. - Terminal:
- MESSAGES in tui/screens/common.ts now maps signup_closed to SIGNUP_CLOSED_MESSAGE.
- The sign-in screen hides the Create account toggle, its key hint and the ctrl+t switch while signupClosed(f.app) is true, and shows the closed message in the header line.
- A new test in test/tui-screens.test.ts checks that no create mode shows, ctrl+t does nothing, and signUp is refused with no account made.
- Dead code: I removed the unreachable
signup_closedentry fromsignupSubmit's error map insrc/web/pages/site.ts.
One addition you didn't ask for: the passkey finish endpoint (POST /signup/passkey) now refuses 403 before it reads the body. Without that, a closed forge answered 400 instead of 403 there, and test/signup.test.ts caught it.
What I verified
- I merged
origin/maininto the branch. - The targeted suites pass:
tui-screens,signup, and the passkey tests, 51 tests. - A full
bun testrun here timed out, so I did not see it finish and the forge's check will be the first full run.tsccould not run here because thebuntype definitions are missing. - My first push of this round had a duplicate
closeddeclaration insrc/tui/screens/signin.tsand a missing import. Both are fixed in the second commit (285d5b31), which is the one to look at.
Left out: invite still behaves like closed until #1143, as the README now says.
Approved. Closed signup, and invite until #1143, now blocks every self-service door to a new account: /signup GET and POST, POST /api/v1/users, both passkey endpoints, the terminal sign-in screen, and a first edge sign-in. All of them go through one predicate, signupClosed(app) in src/actions/users.ts. It reads the setting on every request, so a change applies without a restart. It needs an existing user, so the forge's first account is never refused.
Security: identity still comes only from the verified edge token. An edge name with no account is treated as signed out. It falls through to cookie and token auth exactly as an edge name refused before this change did, so a client cannot claim an identity it does not hold. The passkey endpoints refuse after the same-origin guard and before reading the body, so a closed forge reads no stranger's bytes. Nothing new is built from user text, and the closed page's only dynamic text is the escaped constant.
Other account-making paths I checked and left alone on purpose: everyday (an admin's own second account), claim (a one-time owner link), crew/spawn (agents made by admins) and chaos. None of them is a stranger's signup.
Both doors: the terminal hides Create account, ctrl+t and its key hint, shows the sentence in the header, and signUp itself refuses, so the screen cannot be bypassed. The README now says what happens. The tests cover each door closed and open, invite, the first-account exception on the API and the edge, existing edge accounts, live switching, and the terminal.
Non-blocking:
closedinsignin.tsis read once when the screen is built. A setting changed while the screen is open still refuses at submit, throughsignUp, so this is correct, just less tidy./signinmay still link to Sign up while signup is closed. That is worth a follow-up so a closed forge offers no dead door, but it is not part of this issue.
I could not reach the preview or the screenshots from my sandbox: the network and /data/looks are denied. This verdict rests on reading the code and the tests, which the forge's check runs green.
Clean. I approve.
Closed signup (and invite, until #1143) now stops every self-service door to a new account: /signup GET and POST, POST /api/v1/users, both passkey endpoints, a first edge sign-in, and the terminal's Create account. The check is in the actions (signUp, signUpWithPasskey), not only at each door. Because hasAnyUser is part of the check, a fresh forge's first account is always made. The edge identity is still verified by edgeIdentity before signupClosed is asked. Existing accounts still sign in. The other createUser callers are the everyday account, the claim link, crew members and spawned forges, and each needs an admin or a one-time token, so leaving them alone is correct.
At d2f8b83 the type check is clean, and test/signup.test.ts, test/signup-passkey.test.ts and test/tui-screens.test.ts pass (42 tests, 0 failures), including the new terminal test. The README sentence is replaced with what now happens.
I could not see the preview or the screenshots: my sandbox refused the preview host and the looks directory. So this judgment comes from the code and the tests.
One small point, not blocking: the terminal reads signupClosed once, when the sign-in screen opens. If signup is reopened while the screen is open, Create account stays hidden until the screen is opened again. If it is closed while the screen is open, signUp still refuses, so this cannot make an account.
When
signupisclosedorinvite, no new account is made through the signup page, the API, passkey signup or edge sign-in. I rantscand 162 tests across the signup-related files, including the new ones, and they pass. A fullbun testrun took too long to finish here, so I didn't see it complete.What changed
signupClosed(app)insrc/actions/users.tsis true when the setting is notopenand at least one user exists. It reads the setting on every call, so switching back toopenapplies on the next request with no restart.inviteacts as closed until #1143.signUpandsignUpWithPasskeyreturnsignup_closed, so the password form, the API and passkey signup all refuse.GET /signupand a posted signup form show one sentence, "Signup is closed on this forge.", with a sign-in link and no form, at status 403.POST /api/v1/usersanswers 403{error:"signup_closed", message}. The passkey options and submit endpoints answer 403 too.resolveAuthinsrc/http/server.ts, an edge identity with no account is treated as anonymous while signup is closed. Existing accounts still sign in through the edge.test/signup.test.tscover open, closed and invite on the page, the API and the edge path. They also cover the first-account exception, existing accounts still signing in by edge while closed, and a live switch.Left out
everydayandclaimpages, and the terminal sign-in screen. The terminal screen goes throughsignUp, so it is covered. Theeverydayandclaimpages callcreateUserdirectly. If they make accounts for strangers, they need the same guard — say if you want that done here.To try it
signuptoclosedon/admin, then open/signupand runcurl -X POST /api/v1/users.openand both work again.Closes #2166