archy/docs/companion-pairing-qr.md
Dorian b88609e0ff feat: instant companion pairing — device tokens, named QR, FIPS pair-info
- auth.createDeviceToken / listDeviceTokens / revokeDeviceToken RPCs; only
  SHA-256 hashes persist in data_dir/device-tokens.json
- auth.login accepts {token} (same rate limiter, skips TOTP like remember-me)
- pairing QR now carries name (server name, fallback "My Archipelago"),
  tok (instant login), and FIPS mesh params from new fips.pair-info RPC
  (npub, fips0 ULA, transport ports)
- companion onboarding drops the WireGuard install/tunnel screens — remote
  access moves to the FIPS mesh embedded in the companion app
- fix: fips daemon UDP bind now 2121, matching the published container port
  and fleet rosters (was upstream's 8668 — inbound UDP was dead on bridged
  installs, mesh silently rode TCP 8443)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 22:04:59 +01:00

5.3 KiB

Companion app pairing QR — integration handoff

Status: web-UI side SHIPPED (CompanionIntroOverlay.vue, 2026-07-16). This doc is the contract + requirements for the companion-app side (worked on separately, on the Mac).

What the web UI now does

The "Remote Companion" intro modal (shown once after first dashboard login, and in the public demo) gained a second screen:

  1. Screen 1 (existing): APK download QR (desktop) / download button, plus a new "I've installed it" button to the right of the download button.
  2. Screen 2 (new, slide transition): a pairing QR the companion app scans to auto-fill the server entry, with a Back button returning to screen 1. On small screens (where you can't scan your own display) an "Open in companion app" deep-link button is shown above Back, using the same URI as the QR.

A single URI, also usable as an OS deep link:

archipelago://pair?v=1&url=<origin>&name=<display name>[&tok=<device token>][&pw=<password>][&fnpub=…&fip=…&fhost=…&fudp=…&ftcp=…]

Query parameters:

param required meaning
v yes Payload version, currently 1. Reject/ignore unknown majors gracefully — show "please update the app".
url yes Full origin the app should connect to, scheme included: https://demo.archipelago-foundation.org, http://archipelago.local, http://192.168.1.228, etc. No trailing slash guaranteed either way — normalize.
name no Display name for the server entry. Real nodes send the configured server name, or My Archipelago when it's still the factory default.
tok no Device token minted via auth.createDeviceToken when the QR is rendered. The app logs in with {"method":"auth.login","params":{"token":"…"}} — same endpoint, same rate limiter, skips TOTP (the token was minted from an authenticated session). Long-lived until re-minted (re-showing the pair screen replaces the companion token) or revoked (auth.revokeDeviceToken). Scan → instantly connected, no typing.
pw no Login password. Only present in the public demo (shared demo password entertoexit). Real nodes never embed a password — the frontend doesn't have it.
fnpub no Node's FIPS mesh identity (bech32 npub of the daemon's seed-derived key). Presence of this param means "this node speaks FIPS — mesh with it".
fip no Node's fips0 ULA (IPv6). Once the phone is meshed, the node's UI stays reachable at http://[<fip>] from anywhere — this is the remote-access address (replaces the old WireGuard 10.44.0.1 flow).
fhost no Host the phone's embedded FIPS dials (same host url resolved to).
fudp / ftcp no Mesh transport ports on fhost (currently 2121/udp and 8443/tcp).

Examples the web UI actually emits:

  • Demo: archipelago://pair?v=1&url=https%3A%2F%2Fdemo.archipelago-foundation.org&pw=entertoexit
  • Real node, browsed via LAN IP: archipelago://pair?v=1&url=http%3A%2F%2F192.168.1.228
  • Real node kiosk (UI runs on localhost, so it advertises the mDNS name from system.get-hostname): archipelago://pair?v=1&url=http%3A%2F%2Farchipelago.local

Companion app requirements

  1. Scan entry point: the app's action is labeled "Scan Node's QR" (implemented 2026-07-17; the modal copy in CompanionIntroOverlay.vue was updated to match).
  2. Parse the URI (from camera scan AND from an OS deep-link intent — register the archipelago:// scheme so the "Open in companion app" button on phones works).
  3. On success, create/update a saved server entry:
    • Server address = url exactly as given (respect the scheme — the demo is https, LAN nodes are typically http, .local mDNS names must work).
    • If pw present, prefill the password and attempt auto-login; otherwise land on the password prompt for that server.
    • If an entry with the same origin already exists, update it rather than duplicating.
  4. Demo flow (the showcase): scanning the demo QR should take a fresh install to a logged-in demo session in one step — url https://demo.archipelago-foundation.org, password entertoexit, no manual typing.
  5. Robustness:
    • Tolerate unknown extra query params (forward compat — we may add name, cert fingerprint, etc. under v=1).
    • Self-signed HTTPS on .local/LAN addresses may appear later; don't hard-fail the parse on scheme.
    • Bad/foreign QR → clear error, stay on the scan screen.

Notes / future extensions (not in v1)

  • A real-node pairing token instead of a password (backend mints a one-time token, QR carries it, app exchanges it for a session) — needs a backend RPC; the v param exists so we can bump when this lands.
  • Optional name param (node display name) so the app labels the entry nicely.

Testing checklist (app side)

  • Scan demo QR from https://demo.archipelago-foundation.org → auto-connected demo session.
  • Scan a real node's QR (LAN IP origin) → entry created, password prompt shown.
  • Scan a kiosk node's QR (http://<name>.local) → mDNS resolution works on the phone.
  • Tap "Open in companion app" on a phone browser → deep link opens the app with the same behavior.
  • Re-scan same node → no duplicate entry.