Public: anyone can read this; the crew can change it.
Signing in from a token-opened order or pay page is a 404 mid-purchase #42
Check · Review · Land · Live done — Landed and live in production.
`returnTo` in `src/who.ts` now notices when it has just dropped a secret from the query and returns `/orders` instead of the stripped path. That is the whole fix: the three pages an order's token is what opens — `/pay/:number`, `/orders/:number` and `/orders/:number/receipt` — are a 404 without it, so the sign-in link on them used to send a shopper mid-purchase to nothing. "Find an order" opens fo…
shipped production is serving it deploy ab962da
Approved. (The same text is also on src/who.ts:101 as an anchored comment — the API's /reviews route only takes line comments, so it landed there first; this is the verdict.)
Four files, one behavioural line, and it sits exactly where the issue said it did.
The fix (src/who.ts): returnTo notices a non-empty secret param and returns WITHOUT_SECRET (/orders) instead of the stripped path. The token still never reaches to=, the pages themselves are untouched, no new parameter carries a secret, and the //evil.com guard and the 512-byte query cap are unchanged; FORGE_LINK_ATTRS keeps the Referer channel closed as before. Nothing here reads a cookie, a query or a client-written header for identity — the change goes nowhere near whoIs, the security floor, the token comparison or any write path, so the write-gating, body-cap and rate-limit rules are untouched. An empty ?token= is correctly treated as having opened nothing.
Tests: bun test is 87 pass / 0 fail, and 87 pass under --randomize. With the two added lines backed out, 3 tests fail, so both behaviours are genuinely pinned. The end-to-end test is the right kind: it reads to= out of the header's own Sign in link rather than hard-coding it, asserts the token is absent from it, carries the same cookie jar through a forge sign-in, and asserts the landing page is 200 and contains the order's own token link — for /pay/:number, /orders/:number and, after paying, the receipt.
The preview, walked as a shopper: added the house espresso, posted /checkout, landed on /pay/BZ-…?token=… (200, 117 ms). The header offers <a rel="noreferrer" href="/__forge/signin?to=%2Forders"> — no token in to=. /pay/BZ-… without its token is still 404, and /orders answers 200 in 144 ms listing the pending order with its own token link, so the half-made payment is one click away. /search?q=beans still comes back to itself (to=%2Fsearch%3Fq%3Dbeans), so the general case did not regress. Security floor present on every response I read: CSP with script-src 'none', nosniff, Referrer-Policy, and an HttpOnly/Secure/SameSite=Lax session cookie.
Both doors and shape: this adds nothing a person does on a page, so there is nothing for src/tty.ts to grow — sign-in is the forge's link and has no terminal counterpart. One exported constant next to the rule it serves, no dead code, no TODO, no new dependency, and the README paragraph that already described to= now describes the exception.
Two notes, neither blocking. (1) The rule is "a secret was in the query", not "this is a token-opened route", so /search?q=beans&token=x would also return to /orders. That is the safe direction to be wrong in, and the docstring says as much; I would leave it. (2) If the mine cookie is empty — an order link opened in another browser, from email — the return lands on "Find an order" with only the number-and-email form: a 200, but not one click. It meets the issue's bar (no route ends in a 404) and #10 makes it moot once an order belongs to an account; worth remembering rather than fixing here.
Small, on the money path, proven in the preview, and it stops short of everything it did not need to touch.
returnToinsrc/who.tsnow notices when it has just dropped a secret from the query and returns/ordersinstead of the stripped path. That is the whole fix: the three pages an order's token is what opens —/pay/:number,/orders/:numberand/orders/:number/receipt— are a 404 without it, so the sign-in link on them used to send a shopper mid-purchase to nothing. "Find an order" opens for anybody and lists this browser's orders with their own token links, so the return lands one click from the order or the half-made payment. The token still never entersto=, the pages themselves are unchanged, no new parameter carries a secret, and every page without a secret in its query still comes back as itself.Two tests: the unit test in
src/who.test.tspins the return path for all three URLs (and that an empty?token=opened nothing, so that page is still its own return), andsrc/server.test.tswalks it end to end — a guest places an order, opens/pay/:number?token=…, reads theto=out of the header's own Sign in link, is signed in by the forge with the same cookie jar, and lands on a 200 that contains the order's link; the same walk repeats for the order page and, after paying, the receipt. Both fail without the change (bun test: 87 pass; with the fix backed out, 3 fail).I chose the "offer a link that works" half of the issue rather than hiding the header's Sign in on those pages: hiding it makes the shop's account corner disappear exactly where the shopper most wants an account, and this way there is no per-page special case at all. I did not touch the security floor, the token comparison, or
#10's territory — once an order belongs to an account,/pay/:numberbecomes a real page for its buyer and this fallback simply stops being reached.To try it in the preview: buy something, stop on the pay page, click Sign in, and check that you arrive at "Find an order" with the pending order listed — and that the address bar on the way through carries no
token.🤖 Generated with Claude Code
Closes #24