feat(home-route): inline passkey sign-in on the dashboard - #450
Merged
Conversation
The dashboard now reveals a discreet sign-in banner + header key when whoami reports the viewer isn't signed in on a remote origin, and runs the passkey login ceremony in place over the same strict P2P channel (ownerFetch) — no redirect to /owner-access/login.html, which would spawn a fresh channel with no session. Re-checks on P2P (re)connect. LAN bypass + signed-in views never show it. Closes the discoverability gap (part of #438 seamless UX). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Evolve the inline-signin banner into a real gate: a logged-out remote visitor now sees ONLY a clean sign-in card, never the empty dashboard chrome (which rendered 'No devices configured' and read as an unconfigured instance). The gate covers the dashboard until whoami confirms a signed-in owner; the passkey ceremony runs in place over the strict P2P channel. Suppresses the no-devices prompt while logged out. The transport indicator is now informational (direct/ relayed/connecting), not a confusing toggle that broke the P2P-only channel. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A reload briefly painted the dashboard before next-app.js could cover it with the gate. Add a pre-paint inline <head> script (mirroring the theme-flash guard) that marks <html class=ftw-gated> on a remote origin from the first frame; CSS shows the opaque full-screen gate immediately, and setupAuth() clears ftw-gated once whoami confirms a signed-in owner. The gate is now a class toggle (not the hidden attribute) so the pre-paint CSS can win. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… Pi) Spec for the relay-hardening Fredrik approved: relay serves the sign-in shell + /api/identity from its own embedded copy (Pi not touched), and forwards signaling to the Pi ONLY when it carries a device-key proof the relay can verify. The device-key (LAN-born, wallet-bound) becomes the sole credential that can reach a Pi from the internet, and also mints the session silently. Surface-by-surface threat model + build slices + one open trade-off for review. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mous path to a Pi) Relay serves the sign-in shell (-home-web) + /api/identity (from -home-pubkey) itself, so anonymous visitors never reach the Pi. Signaling is gated by a device-key proof (relay-issued nonce, signed by a non-extractable WebCrypto ECDSA P-256 key minted at LAN enrollment, verified against the device-pubkeys the Pi publishes on /me/register) — else 403, Pi never woken. The device-key also mints the session silently over the channel (device-PoP); step-up keeps a passkey; revocation drops the key on Pi + relay. Built in parallel across relay/ Pi/browser against locked wire contracts (C1-C4), then integrated. All Go unit tests + 97 web tests pass. Pending Codex security GO before live. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codex returned NO-GO on the parallel build; these close the findings relevant to the shippable relay-hardening (slices 1+2): - #1 CRITICAL: require -home-web when a home host is set (else the relay fell back to forwarding anonymous GETs to the Pi — the exact surface we forbid). requireHomePin now enforces it. - #2 HIGH: device_pubkeys are now covered by the registration signature (MeRegisterSigningStringV2); a captured /me/register can no longer be replayed with a swapped/added device key. + regression test. - #4 MED: symlink-escape in the -home-web static server closed (EvalSymlinks + re-check the resolved path stays under the web root). Deferred to the C2-enforcement phase (inert until the device-key gate is ON), with TODOs: #3 (bind device_pubkey to the enroll ceremony) and #5 (separate per-IP limiter on challenge issuance). All Go unit + 97 web tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ault) Slices 1+2 (relay serves the shell + /api/identity itself, closing the anonymous-FETCH surface) ship independently of the device-key signaling gate: -require-device-key defaults OFF, so signalBrowserOffer parks the raw offer with no proof (pre-C2 behaviour) and a home Pi that doesn't yet publish device-keys keeps working. Flip it on once device-keys are enrolled to close the anonymous-SIGNALING surface. C2 tests now assert the enforced path; a new test asserts the dormant default. All Go unit + web tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the discoverability gap in the P2P-only home route: the dashboard now is the door. When you open
home.fortytwowatts.comand aren't signed in, a discreet "Sign in with your passkey" banner (+ a header key) appears, and the passkey ceremony runs in place over the same strict P2P channel — no redirect to/owner-access/login.html(which would spawn a fresh channel with no session).whoami-driven: shown only when not signed in on a remote origin; hidden on the LAN bypass and once signed in. Re-checks when the P2P channel (re)connects (whoami needs the channel up to answer).ownerFetch(strict / FIX-B) — login/start, login/finish, whoami, logout never traverse the relay in cleartext. The public-route fetch guard still passes.Part of the #438 seamless-UX layer (the inline-login slice). Device-key silent re-auth + the discreet transport indicator are the remaining/already-shipped pieces.
Verification
home.fortytwowatts.com: the banner + header key appear when unauthenticated, over a live● DIRECTchannel (screenshot in chat).node --test web/**green incl. the public-route fetch guard;next-app.jsparses.Test plan
home.*off-WiFi → Face ID → dashboard populates with live data