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

Claim links: the secret in the URL fragment, and the spawner can re-issue one #836

Merged opened this in answer to

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

Merged

Closes #729 Closes #730

shipped production is serving it for issue #729 — its issue is already done deploy d5b2fe2

opened this

Closes #729 Closes #730

The claim link, round two.

  • The secret moves into the URL fragment, https://child/claim#<token>. A fragment never leaves the browser: no server, edge or proxy log and no Referer sees it. The page reads it, drops it from the address, and posts it in the form body.
  • Only the spawner re-issues a link (an admin of the parent), and only for a child nobody has claimed. The old link dies at once.

The path: the owner sent a link that went unused. From the API or the MCP he asks for a new one and sends that. The person opens it, sees "This forge is yours" and claims it.

Work in progress. This first commit is the refusing tests. The code, docs and looks follow.

commented

Ready for review (replaces the description above). Closes #729, closes #730.

#730: the secret in the fragment.

  • claimUrl is now https://<child>/claim#<token>. A fragment never leaves the browser: it is not in the request line, not in a Referer, and not in any server, edge or proxy log.
  • GET /claim serves the form with no secret in it. A small script (the page's nonce'd script) moves the code from location.hash into a hidden token field and calls history.replaceState to drop it from the address bar. Without JavaScript the "Claim code" field stays visible and takes a paste.
  • POST /claim reads the token from the form body only, never from a query string.
  • Links in the old /claim/<token> form keep working.
  • An unclaimed forge's sign-in page now leads to /claim ("Was this forge made for you? Claim it with your code"), so the page is reachable from inside the forge too (scripts/hollow.ts). The link goes away once the forge is claimed.
  • Verified in a real browser: after opening /claim#… the address reads /claim. No request URL or Referer the page made contained the token. The claim landed on /.

#729: the spawner re-issues a link.

  • POST /api/v1/forges/:name/claim-link (rw token or same-origin session; site admins of the parent only) and the MCP claim_link {name}.
  • The child keeps one claim row, so the new link replaces it and the old one dies at once ("not a claim link … or replaced by a newer one").
  • A claimed child (a person is its admin) gets 409 "already claimed; ask its owner to invite you". An unknown name gets 404.
  • The new link is shown once. An idempotent replay hides it (SHOWN_ONCE covers claimUrl). An event forge.claim_reissued is recorded.

The path: the owner's first link went unused or was lost. He asks the API or the MCP for a new one and sends it. The person opens it, sees "This forge is yours", claims it and lands home.

Removes: nothing. It adds one route and one MCP tool, and one line on an unclaimed forge's sign-in page.

Tests: test/claim-link.test.ts (10, in the gate), refusing cases first:

  • the link is in fragment form; the page holds no token
  • a token in a query string is ignored (404, no account)
  • bob's rw token and the admin's ro token get 403; an unknown child gets 404
  • the re-issued link works and the old one is refused; a claimed child gets 409
  • an idempotent replay hides the link
  • the sign-in door shows on an unclaimed child and not on the parent, and goes away once claimed

The older spawn tests read the token from #. Required suites plus spawn-*, mcp and onboarding pass (299). tsc is clean.

Looks (390 px): the claim page opened from a fragment link shows "This forge is yours", the access sentence and username, email and password, with the claim-code field hidden because the script filled it.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs