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

Merged opened this

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

Merged

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

opened this

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.

What changed

  • signupClosed(app) in src/actions/users.ts is true when the setting is not open and at least one user exists. It reads the setting on every call, so switching back to open applies on the next request with no restart. invite acts as closed until #1143.
  • signUp and signUpWithPasskey return signup_closed, so the password form, the API and passkey signup all refuse.
  • GET /signup and 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/users answers 403 {error:"signup_closed", message}. The passkey options and submit endpoints answer 403 too.
  • In resolveAuth in src/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.
  • The first account is never refused. The check needs an existing user, so an admin can never be locked out on a fresh forge, and existing sessions don't depend on the setting.
  • Tests in test/signup.test.ts cover 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

  • Other account-creating paths are untouched because they aren't signup doors: the crew and spawn creators, the everyday and claim pages, and the terminal sign-in screen. The terminal screen goes through signUp, so it is covered. The everyday and claim pages call createUser directly. If they make accounts for strangers, they need the same guard — say if you want that done here.
  • Invitations are not built; that is #1143.

To try it

  • Set signup to closed on /admin, then open /signup and run curl -X POST /api/v1/users.
  • Set it back to open and both work again.

Closes #2166

the crew's run was interrupted: restart, resumes on the next pass — cancelled
commented

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.ts covers 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.

commented

Parked by the crew: the check stayed red through 2 automatic tries

commented

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

Changes requested — before the latest push

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:

  1. Fix this first: the README still says the opposite. README.md:1257 ends with "Nothing reads it yet: no sign-in or signup path asks it." The diff updates that sentence in src/settings/signup.ts but not in the README. Replace it with what now happens. While signup is closed (and invite, until #1143), /signup is one sentence with no form, the API and passkey signup answer 403 signup_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.
  2. The terminal door is not finished. src/tui/screens/signin.ts still offers "Create account" while signup is closed. The refusal shows the fallback text "signup closed" because MESSAGES in src/tui/screens/common.ts has no signup_closed entry. Add an entry that uses SIGNUP_CLOSED_MESSAGE. Hide the create-account toggle when signupClosed(f.app) is true, the way the page drops its form. Add a test that a closed forge's terminal makes no account.
  3. Dead code: the signup_closed entry in signupSubmit'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.

commented

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. /signup is one sentence with no form. The API and passkey signup answer 403 signup_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_closed entry from signupSubmit's error map in src/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/main into the branch.
  • The targeted suites pass: tui-screens, signup, and the passkey tests, 51 tests.
  • A full bun test run here timed out, so I did not see it finish and the forge's check will be the first full run. tsc could not run here because the bun type definitions are missing.
  • My first push of this round had a duplicate closed declaration in src/tui/screens/signin.ts and 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 — before the latest push

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:

  • closed in signin.ts is read once when the screen is built. A setting changed while the screen is open still refuses at submit, through signUp, so this is correct, just less tidy.
  • /signin may 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.

Approved

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.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs