feat: connection candidates refresh + relay identity hardening
Candidates refresh (server + mobile + desktop clients):
- GET /api/client-auth/connection/candidates returns the server's current
LAN URLs, relay candidate, and serverId for already-paired devices
- /health and /api/version expose serverId so clients can verify a learned
address belongs to the expected server before sending their bearer token
- mobile: refresh saved candidates over the live transport after every
connect/wake, hot-switch relay->LAN when a fresh address is reachable;
serverId gate on direct probes; token no longer sent to /health
- desktop: refresh stored host apiUrl after a relay connect and hot-switch
back to direct; electron probe verifies serverId before authenticated fetch
Fixes found while debugging a dead pairing:
- settings: strict reader that throws on corrupt/unreadable file instead of
returning {}; relay signing/encryption key generation is now gated on it,
so a swallowed read failure can no longer mint a new server identity and
orphan every paired device (loud log when a keypair IS generated)
- SessionAuthGate: bounded auto-retry for transient session-check failures
(initial request racing the relay tunnel's first WS attempt, startup 5xx)
This commit is contained in:
@@ -58,13 +58,16 @@ export const createRelayService = ({
|
||||
crypto,
|
||||
readSettingsFromDiskMigrated,
|
||||
writeSettingsToDisk,
|
||||
// Strict settings reader (throws on corrupt/unreadable) gating identity
|
||||
// regeneration — see identity.js/signing-key.js.
|
||||
readSettingsStrict,
|
||||
getLocalPort,
|
||||
// Returns true when any paired device or pending pairing session uses the
|
||||
// relay transport. The relay lifecycle is driven purely by this demand.
|
||||
hasRelayDemand = async () => false,
|
||||
logger = console,
|
||||
}) => {
|
||||
const identityRuntime = createRelayIdentityRuntime({ crypto, readSettingsFromDiskMigrated, writeSettingsToDisk });
|
||||
const identityRuntime = createRelayIdentityRuntime({ crypto, readSettingsFromDiskMigrated, writeSettingsToDisk, readSettingsStrict });
|
||||
|
||||
let hostClient = null;
|
||||
let status = { state: 'disabled', lastError: null, connectedClients: 0 };
|
||||
@@ -145,6 +148,15 @@ export const createRelayService = ({
|
||||
}
|
||||
};
|
||||
|
||||
// Stable server identity (base64url SHA-256 of the canonical public signing
|
||||
// JWK). Derived from a public key, so it is not a secret; clients use it to
|
||||
// verify that a learned/probed address belongs to this server before trusting
|
||||
// it. Independent of whether the relay host is currently enabled.
|
||||
const getServerId = async () => {
|
||||
const identity = await identityRuntime.getRelayIdentity();
|
||||
return identity.serverId;
|
||||
};
|
||||
|
||||
const getStatus = async () => {
|
||||
const config = await readConfig();
|
||||
const identity = await identityRuntime.getRelayIdentity();
|
||||
@@ -241,6 +253,7 @@ export const createRelayService = ({
|
||||
reconcile,
|
||||
stop,
|
||||
getStatus,
|
||||
getServerId,
|
||||
getPairingCandidate,
|
||||
ensureEnabledForPairing,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user