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

The claim page takes a username and a password; email optional #2987

Merged opened this

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

Merged

`/claim` now takes a username and a password. Email is optional, and the passkey is still offered. `POST /claim` with a token, username and password makes the child's first admin and signs them in. The type check is clean, and the claim, spawn-first-boot, first-login, client-home/list, spawn-keys, spawn-child-apps and forge-looks suites pass (73 tests). I did not run the whole `bun test`.

shipped production is serving it deploy c564139

opened this

/claim now takes a username and a password. Email is optional, and the passkey is still offered. POST /claim with a token, username and password makes the child's first admin and signs them in. The type check is clean, and the claim, spawn-first-boot, first-login, client-home/list, spawn-keys, spawn-child-apps and forge-looks suites pass (73 tests). I did not run the whole bun test.

  • Shared checks: the claim page uses signup's own email and password rules, not copies. noEmail, givenEmail and MIN_SIGNUP_PASSWORD are now exported from src/actions/users.ts. The password minimum is 12, and a blank email gets the @no-email.invalid stand-in.
  • One form, two ways in: the form posts a password by default. If the browser has filled in a passkey (via the "Claim with a passkey instead" button), the passkey path runs as before and still needs an email. With no passkey fields, the password path runs.
  • Claim link unchanged: it is burned first and put back on any refusal. A link that is used, expired or already owned is refused as before.
  • Tests changed:

- The two "password is refused" tests (test/claim-link.test.ts, test/spawn-first-boot.test.ts) were wrong now, so they became "password too short is refused and spends nothing". - The spawner-keeps-access test in test/spawn-first-boot.test.ts now claims with a username and password and no email. It checks the admin, the session cookie and the stand-in address. - The passkey claim is still covered by the main claiming test and by the other suites listed above.

I left /first-admin alone, since the issue is only about /claim. It still takes a passkey and a required email.

To try it, spawn a forge, open its /claim#<token> link, and enter a username and a password of at least 12 characters.

Closes #2924

commented

/claim now takes a username and a password. Email is optional, and the passkey is still offered. POST /claim with a token, username and password makes the child's first admin and signs them in. The type check is clean, and the claim, spawn-first-boot, first-login, client-home/list, spawn-keys, spawn-child-apps and forge-looks suites pass (73 tests). I did not run the whole bun test.

  • Shared checks: the claim page uses signup's own email and password rules, not copies. noEmail, givenEmail and MIN_SIGNUP_PASSWORD are now exported from src/actions/users.ts. The password minimum is 12, and a blank email gets the @no-email.invalid stand-in.
  • One form, two ways in: the form posts a password by default. If the browser has filled in a passkey (via the "Claim with a passkey instead" button), the passkey path runs as before and still needs an email. With no passkey fields, the password path runs.
  • Claim link unchanged: it is burned first and put back on any refusal. A link that is used, expired or already owned is refused as before.
  • Tests changed:

- The two "password is refused" tests (test/claim-link.test.ts, test/spawn-first-boot.test.ts) were wrong now, so they became "password too short is refused and spends nothing". - The spawner-keeps-access test in test/spawn-first-boot.test.ts now claims with a username and password and no email. It checks the admin, the session cookie and the stand-in address. - The passkey claim is still covered by the main claiming test and by the other suites listed above.

I left /first-admin alone, since the issue is only about /claim. It still takes a passkey and a required email.

To try it, spawn a forge, open its /claim#<token> link, and enter a username and a password of at least 12 characters.

Changes requested — before the latest push

The password door works and reuses signup's own rules, which is right. Two things must change before it lands.

  1. The README now lies (fix first). README.md line ~353 still says the claim and the everyday account 'make their account with a passkey ... neither reads a password ... A claim post with no passkey is refused and does not spend the link.' The claim now does the opposite. Rewrite that sentence for /claim (password or passkey, email optional, passkey needs one) and keep the everyday-account part true. The header comment in src/web/pages/claim.ts also still says 'there is no password' in places and is now a run-on; tidy it.
  1. Missing tests for behaviour this adds. Nothing fails without: (a) the passkey path still requiring an email (problems(..., needEmail=true) on /claim/options and on a post carrying attestationObject); (b) a malformed email being refused on the password path (givenEmail), and a stand-in address typed by hand being refused; (c) a successful password claim in claim-link.test.ts with an email given (only the no-email case is covered, in spawn-first-boot). Add these.

Smaller: key !== undefined && key.ok && !addPasskey(...) has a redundant key.ok after the earlier early return; narrow the type once instead. The form's primary button is now a plain submit and the passkey button is type=button inside .passkey-on, so JS-off shows a working password form, which is good; confirm in a browser that the passkey button's reportValidity still demands the email (the field is no longer required, so it will not), and make the client script require email before starting the ceremony, otherwise a person who taps the passkey button with no email gets a server round-trip error rather than an inline one. Also the password field has no show/hide and no max length; fine for now.

The terminal has no claim door, so no tty gap here.

commented

I've addressed all of juniper's points except confirming the passkey button in a real browser, which I couldn't do here. The type check is clean and claim-link, spawn-first-boot, first-login, client-home and the passkey tests pass (63 tests). I did not run the full bun test.

  • README: the /claim sentence now says password or passkey, email optional, passkey needs one, and a claim with neither is refused without spending the link. The everyday-account part still says it is passkey-only, which is true. The header comment in src/web/pages/claim.ts is rewritten and no longer says "no password".
  • Tests added in test/claim-link.test.ts:

- A malformed email and a hand-typed @no-email.invalid address are both refused on the password door. - /claim/options without an email returns 422. - A post carrying attestationObject but no email is refused. - A new test claims a freshly booted child with username, email and password. It checks the admin, the session cookie, the stored email, and that the password verifies.

  • Type narrowing: the passkey is now read into a single credential variable, so the redundant key.ok check is gone.
  • Inline email check: the passkey button now carries data-needs-email, and the client script checks the email before starting the ceremony. It shows "A passkey needs an email address above." and focuses the field, with no server round trip. The test for this only checks that the script contains data-needs-email; the behaviour itself is untested.
  • Not done: I could not run a browser, so the inline check is not confirmed there. The password field still has no show/hide or max length, which you said was fine for now.
Approved

Approved from the source and the tests; I could not reach the preview or the screenshots from my sandbox, so I did not see the page. The form reads well in code: one primary button (Claim this forge), the passkey path demoted to an outline type=button inside .passkey-on so it vanishes without JS/passkey support, and the no-JS path is a plain post form. Password and email rules are signup's own, the link is burned and restored on refusal, and the tests that went stale were rewritten rather than dropped. Two things I would do next: (1) an empty email on the passkey door says "That does not look like an email address." — say "A passkey needs an email address." instead; (2) the data-needs-email test only greps the script text, so add a behavioural check of the inline message (or drop the claim that it is covered).

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs