- 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>
88 lines
5.3 KiB
Markdown
88 lines
5.3 KiB
Markdown
# 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.
|
|
|
|
## The QR payload / deep link (the contract)
|
|
|
|
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.
|