feat: pairing v2 — one-tap trusted devices over LAN and private relay (#2103)
Reworks how devices connect to an OpenChamber server, end to end. Pairing v2: - One-time pairing links/QR codes (openchamber://connect?v=2) carrying a set of transport candidates (LAN/tunnel/relay) and a single-use secret redeemed server-side; no tokens embedded in links - Add-a-device dialog written for first-time users: intent-based transport choice (Anywhere / Home network only / This computer only) with plain-language descriptions, transparent fallback checkboxes, server-authoritative LAN detection, high-res QR dialog - Private relay folded into pairing as a transport candidate with a demand-driven lifecycle (enables when a relay device is paired, disables when none remain) Multi-transport devices: - A saved device holds all its transports and one token; mobile re-probes on connect, resume, and network change and hot-switches LAN<->relay seamlessly (no re-pairing, no remount, session preserved) - Desktop can import relay pairing links, switch to relay hosts through the E2EE tunnel, and restore a relay default host after relaunch Device management: - Device list (web + desktop) shows live per-device connectivity with the active transport (Connected - Local network / Relay) and platform badges (iOS/Android/macOS/Windows/Linux) - One physical device = one record: stable per-install dedupe keys across pairing and password re-login; typed pairing label names the device, paired devices name the connection by the issuing server hostname - Trusted desktop-local client manages all devices (list, revoke, clear revoked); relay host reaps dead client sockets after 3 missed keepalives Android: - LAN transport unblocked (cleartext + mixed content, mirroring iOS ATS exceptions); resume re-probe retries through network flux and silently auto-reconnects from a disconnected state
This commit is contained in:
@@ -3,9 +3,15 @@
|
||||
## Purpose
|
||||
This module owns OpenChamber UI authentication for browser access, including password session auth, WebAuthn passkeys, and trusted-device session handling.
|
||||
|
||||
Trusted-device access has one durable credential model: a remote client bearer token stored by `packages/web/server/lib/client-auth/remote-clients.js`. Password, passkey, and Pairing v2 are issuance methods for that credential, not separate credential systems. Issued client tokens are returned once, stored server-side only as hashes, and are later authenticated via `Authorization: Bearer oc_client_...`.
|
||||
|
||||
Pairing v2 is implemented by `packages/web/server/lib/client-auth/pairing.js`. It stores short-lived one-time pairing sessions with hashed secrets, exposes create/cancel/redeem routes under `/api/client-auth/pairing/*`, and redeems a valid pairing secret into the same remote client token used by password/passkey trusted-device flows.
|
||||
|
||||
## Entrypoints and structure
|
||||
- `packages/web/server/lib/ui-auth/ui-auth.js`: UI auth controller runtime, cookie/session issuance, rate limiting, and auth route handlers.
|
||||
- `packages/web/server/lib/ui-auth/ui-passkeys.js`: passkey store and WebAuthn registration/authentication verification helpers.
|
||||
- `packages/web/server/lib/client-auth/remote-clients.js`: trusted-device client token storage, bearer authentication, last-used tracking, and revocation.
|
||||
- `packages/web/server/lib/client-auth/pairing.js`: short-lived Pairing v2 sessions and one-time secret redemption into trusted-device client tokens.
|
||||
|
||||
## Public exports (ui-auth.js)
|
||||
- `createUiAuth({ password, cookieName, sessionTtlMs, readSettingsFromDiskMigrated })`: creates UI auth controller with methods:
|
||||
|
||||
@@ -829,6 +829,11 @@ export const createUiAuth = ({
|
||||
expiresAt: new Date(Date.now() + ttlMs).toISOString(),
|
||||
clientKind: req.body?.clientKind,
|
||||
dedupeKey: req.body?.dedupeKey,
|
||||
authMethod: 'password',
|
||||
deviceName: req.body?.deviceName,
|
||||
devicePlatform: req.body?.devicePlatform,
|
||||
deviceModel: req.body?.deviceModel,
|
||||
appVersion: req.body?.appVersion,
|
||||
});
|
||||
}
|
||||
res.setHeader('Cache-Control', 'no-store');
|
||||
@@ -892,6 +897,11 @@ export const createUiAuth = ({
|
||||
expiresAt: new Date(Date.now() + ttlMs).toISOString(),
|
||||
clientKind: req.body?.clientKind,
|
||||
dedupeKey: req.body?.dedupeKey,
|
||||
authMethod: 'passkey',
|
||||
deviceName: req.body?.deviceName,
|
||||
devicePlatform: req.body?.devicePlatform,
|
||||
deviceModel: req.body?.deviceModel,
|
||||
appVersion: req.body?.appVersion,
|
||||
});
|
||||
}
|
||||
res.json({
|
||||
|
||||
Reference in New Issue
Block a user