diff --git a/docs-developers/frontend/connect-and-addresses.md b/docs-developers/frontend/connect-and-addresses.md index 00ecdc2..9d497e8 100644 --- a/docs-developers/frontend/connect-and-addresses.md +++ b/docs-developers/frontend/connect-and-addresses.md @@ -151,6 +151,31 @@ home pick is dropped the moment the device changes network, before anything else sent. Any new code path that talks to a home address must keep this order. ::: +### Pairing at a self-signed server's home HTTP + +A server on its own (self-signed) certificate also answers plain HTTP to the home +network ([Home HTTP](../server/configuration.md#home-http-tlshome_http)), and its pairing +links carry `home=http://` beside an `https://` base, plus the server's id as +`sid=` (`parsePairingScan` reads it into `PairingScan.serverId`). The app can't trust +the certificate, so `useLinkPairing` exchanges at the base first and, only when that +couldn't connect (`withFallback`: never after an `ApiError`, the server's own answer), +asks `pairingFallback` whether to try the home address once. It says yes only when all +of these hold: native (an https page can't call http), not a timeout (a host that never +answered would only time out again), the base is https and the home is plain http on +the **same host and port** (an absent port is 443 for https, 80 for http), the link +carries a `sid`, and a tokenless `GET /api/v1/server` answers with exactly that +id. The exchange's own `server_id` must match too. The connection is stored with the +https base as `serverUrl` (it fails safely by TLS on another network) and the home as +its address, and `finishConnect` sets the home pick (`answeredAt`) before the connection +is stored, so its first requests go home; the address runner then keeps it by the usual +probe. Android allows cleartext for this (`expo-build-properties` +`android.usesCleartextTraffic`); iOS's default ATS allows local networking (IP +addresses, `.local` and single-label names), not a `.lan` or `.home.arpa` name. + +When pairing or the address field can't reach an https server at all, the message +names a self-signed certificate as a likely cause (`selfSignedMayBeWhy`: native, https, +not a timeout, and no http retry was made). + ### Where the pick lives (`src/api/address-route.ts`) The pick is **in memory only**: `useAddressRoute` holds `{ [connectionId]: url }`; diff --git a/docs-developers/server/api/reference.md b/docs-developers/server/api/reference.md index 6dc0bce..5fd071f 100644 --- a/docs-developers/server/api/reference.md +++ b/docs-developers/server/api/reference.md @@ -181,10 +181,11 @@ Response `200` - the pairing payload (`PairingPayload` in `internal/api/qr.go`): ```json { "server_name": "AudioSilo", + "server_id": "kx8Qz1c7m2Vw0aB3dEfGh", "base_url": "https://books.example.com", "pairing_token": "3vJx0eKQm9WZbT5nR8sHc2fLdA7yUqPgVi4oXk1NwsE", - "uri": "audiosilo://connect?server=https%3A%2F%2Fbooks.example.com&token=3vJx0eKQ…", - "web_url": "https://books.example.com/web/connect?token=3vJx0eKQ…", + "uri": "audiosilo://connect?server=https%3A%2F%2Fbooks.example.com&token=3vJx0eKQ…&sid=kx8Qz1c7m2Vw0aB3dEfGh", + "web_url": "https://books.example.com/web/connect?token=3vJx0eKQ…&sid=kx8Qz1c7m2Vw0aB3dEfGh", "qr_png_data_uri": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg…", "links": { "web": "https://books.example.com/web", @@ -211,7 +212,11 @@ app via Universal/App Links on claimed domains, else the embedded web player); `uri` is the custom-scheme equivalent for an explicit "Open in app" action. `links.ios`/`links.android` (store links) are omitted until the store apps ship. `base_url` honors the configured `public_url`, falling back to the request host. -`server_name` is the server's display name, as `GET /server`'s `name`. +`server_name` is the server's display name, as `GET /server`'s `name`, and +`server_id` its stable id, as `GET /server`'s `server_id`. Both links end with that id as +`sid=` (after the address params below; absent only on a server without an id, which +the launcher always mints), so a client can check who answers at an address before +sending the token there. `addresses` is the server's [home and away addresses](#home-and-away-addresses), omitted when it has neither; both links then carry them too (below). @@ -224,7 +229,7 @@ answers, and [`GET /addresses`](#get-apiv1addresses) carry the same object: | Field | Type | Meaning | |---|---|---| -| `home` | string, optional | The server's address on the household network: the configured [`lan_url`](../configuration.md#home-address-lan_url), else the address **this request** arrived on (scheme + `Host`) when that is a home-network host and the request did not come through a proxy ([the rule](../configuration.md#home-address-lan_url)) | +| `home` | string, optional | The server's address on the household network: the configured [`lan_url`](../configuration.md#home-address-lan_url), else the address **this request** arrived on (scheme + `Host`) when that is a home-network host and the request did not come through a proxy ([the rule](../configuration.md#home-address-lan_url)). On a self-signed server with [home HTTP](../configuration.md#home-http-tlshome_http) the derived one is always `http://`, on the request's port | | `away` | string, optional | The address that works from anywhere: the configured `public_url` | A `home` equal to the `away` is dropped. On the pairing payloads and the sign-in answers @@ -236,11 +241,25 @@ The two links carry the same addresses as extra query parameters **after** the e ones, only those that are set, so an older client ignores them: ```text -audiosilo://connect?server=https%3A%2F%2Fbooks.example.com&token=…&away=https%3A%2F%2Fbooks.example.com&home=http%3A%2F%2F192.168.1.20%3A8080 -https://books.example.com/web/connect?token=…&away=https%3A%2F%2Fbooks.example.com&home=http%3A%2F%2F192.168.1.20%3A8080 +audiosilo://connect?server=https%3A%2F%2Fbooks.example.com&token=…&away=https%3A%2F%2Fbooks.example.com&home=http%3A%2F%2F192.168.1.20%3A8080&sid=kx8Qz1c7m2Vw0aB3dEfGh +https://books.example.com/web/connect?token=…&away=https%3A%2F%2Fbooks.example.com&home=http%3A%2F%2F192.168.1.20%3A8080&sid=kx8Qz1c7m2Vw0aB3dEfGh ``` -Read them by name: the encoder sorts them (`away` before `home`). +Read them by name: the encoder sorts them (`away` before `home`), and `sid` comes last. + +On a self-signed server with [home HTTP](../configuration.md#home-http-tlshome_http) (the +default), the port also answers plain HTTP to home-network clients, and a derived `home` +is `http://` even when the request came over https: a device pairing at +`https://192.168.1.20:8080` gets `home` `http://192.168.1.20:8080` (an https request +without a port gets `http://:443`, the port that answers). `base_url`, `web_url` +and the `uri`'s `server=` stay https, even for a pairing asked for over plain HTTP +(the https base on the same port, `:80` for a Host without one), so a link reads +`…server=https%3A%2F%2F192.168.1.20%3A8080&token=…&home=http%3A%2F%2F192.168.1.20%3A8080&sid=…`. +A native app that can't trust the certificate at `server` exchanges the token at that +`home` instead, when it has the same host and port and a tokenless `GET /api/v1/server` +answers with the link's `sid` (the token then travels in cleartext, so it must reach this +server and no other box at the same private address); the player's rule is in +[Connect and home/away addresses](../../frontend/connect-and-addresses.md). A client must not send a token to a `home` address until a tokenless `GET /api/v1/server` there has answered with the same `server_id`: a private @@ -6073,6 +6092,7 @@ and `AUDIOSILO_*` variable each one is, and whether it needs a restart (see "network": { "bind": "0.0.0.0:8080", "tls_mode": "selfsigned", + "tls_home_http": true, "tls_hosts": [], "trusted_proxies": ["10.0.0.2/32"], "cors_origins": [] @@ -6112,7 +6132,7 @@ and `AUDIOSILO_*` variable each one is, and whether it needs a restart (see }, "locked": { "players.web_dir": "AUDIOSILO_WEB_DIR" }, "restart_settings": [ - "network.bind", "network.tls_mode", "network.tls_hosts", "players.web_dir", + "network.bind", "network.tls_mode", "network.tls_home_http", "network.tls_hosts", "players.web_dir", "metadata.base_url", "metadata.mode", "demo.enabled", "demo.idle_ttl", "backups.dir" ], "restart_pending": [] diff --git a/docs-developers/server/auth-and-security.md b/docs-developers/server/auth-and-security.md index fc146a5..6d4b33b 100644 --- a/docs-developers/server/auth-and-security.md +++ b/docs-developers/server/auth-and-security.md @@ -690,6 +690,13 @@ book's audio and cover as no-cors `