Invite setup page (HTML+JS) #5
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
posta/server#5
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What to build
The browser-side companion to #4. When a user clicks
https://<id-url>/setup#invite=pinv_…, the daemon serves a small HTML+JS page that:window.location.hashto extract the invite tokenGET /api/v1/invite/info?invite=...— does NOT consume the invite, just validates and fetches identity name + device-hint for prefilling/api/v1/invite/redeemwith{invite, deviceName}, gets{token, identity, deviceName}mst_…plaintext once with a copy button + aposta://pair?token=…&url=…deep-link button (no-op until iOS/TUI clients handle the scheme — but the link is there now so it keeps working when clients ship)Visual treatment matches the landing card from commit
793e9ed(paper/sky/forest palette, Newsreader/Schibsted/IBM Plex Mono, embedded HTML+CSS, ~30 lines of vanilla JS, no framework).The fragment-not-query design keeps the secret out of access logs — JS reads it client-side and posts it over the body of the redeem call.
Acceptance criteria
GET /setupserves the HTML page (content-negotiated alongside the existing landing card path)/setupand/api/v1/invite/*)#invite=fromwindow.location.hashdeviceHintposta://pair?...deep linkBlocked by
Agent Brief
Category: enhancement
Summary: Serve a small embedded HTML+JS page at
GET /setupthat drives the user-facing half of the invite redemption flow defined in #4.Blocked by: #4 (the
/api/v1/invite/infoand/api/v1/invite/redeemendpoints must exist for the page to call).Current behavior:
There is no setup page. New devices get bearer tokens by the operator running
posta-server token createand copying the plaintext out of band. A user clicking a future invite URL would see a 404. The only existing browser-facing surface is the landing card (commit793e9ed) served on/forAccept: text/html— it shows the actor doc as a styled page, no interaction.Desired behavior:
When a user opens
https://<identity-url>/setup#invite=pinv_…, the daemon serves a small embedded HTML+JS page that:window.location.hashclient-side to extract the invite token. The token never appears in a query string and therefore never lands in access logs.GET /api/v1/invite/info?invite=<plaintext>to validate without consuming. On success, the response carries the identity URL/name and an optionaldeviceNamehint.deviceNamefrom the info response if present) and a "Pair" button. No auto-submit.{invite, deviceName}to/api/v1/invite/redeem. On success, displays the returnedmst_…plaintext token exactly once with a copy-to-clipboard button and aposta://pair?token=…&url=…deep-link button. The deep link is a no-op until native clients implement the scheme — including it now means it lights up automatically when iOS/TUI ship./inforeturns 410 for both without distinguishing./setupwith no#invite=fragment, renders an "incomplete link" message asking the user to use the full URL the operator sent.<noscript>fallback explaining that pairing requires JS — no progressive-enhancement fallback path exists because the whole flow depends on reading the fragment.Visual treatment:
Match the landing card precedent at
internal/inbox/{landing.go, landing.html}://go:embedandhtml/templateRouting and middleware:
/setupis a per-identity route registered on the same handler tree as/api/v1/*— it lives under the multi-tenant runner per #1 and shares the same Host-based dispatch./api/v1/invite/*) extends to cover/setup. The page itself is unauthenticated; the/redeemcall inside it is what mints credentials.Key interfaces:
GET /setupper identity that responds with the embedded HTML,Content-Type: text/html; charset=utf-8, and a saneCache-Control(the page is static-from-binary, so a long cache is fine).landing.html) containing the markup, styles, and inline JS./setupand/api/v1/invite/*.Acceptance criteria:
GET /setupreturns the HTML page with the right content type, in every per-identity runner. Wrong-host requests still 404 per #1./setupand/api/v1/invite/*.#invite=...client-side fromwindow.location.hash. The token never appears in a query parameter.infoon load,redeemon submit. There is no auto-redeem on page load (preventing link-preview crawlers from burning invites)./infosucceeds, the device-name input is pre-filled withdeviceNamefrom the response (or empty ifnull)./redeem, the page displays themst_…plaintext exactly once, a copy-to-clipboard button that works in modern browsers, and aposta://pair?token=<plaintext>&url=<identity-url>deep-link button./setupwith no#invite=fragment, the page renders an incomplete-link message.<noscript>fallback explains JS is required.Out of scope:
posta://deep link. The button is wired but does not activate native clients yet — that lands when iOS or TUI ship URL-scheme handlers.mst_…is a normal long-lived bearer token like any other.