/
/
/
# What does this implement/fix? The built-in web player needs to be updated to follow the latest Specification, which means encryption support. This PR adds a new `sendspin/pair_web_player` API command, which allows passing in a pairing token to authenticate the Web player, and other integrated clients (Mobile App, Desktop App, etc.). The command takes the client's own pairing token, derives the client id from the token itself, and delegates to the existing `pair_with_token`, producing a regular pairing. Deriving the id from the token is what makes the command work on every transport, including HA Ingress and the remote WebRTC gateway, where the session carries no client id. A client that is already paired takes a silent no-op path, so the Web player can safely call this on every connect. ## Pairing lifetime Party guests pair their own Web player too, so the command is available at the `players.control` scope, and a pairing must therefore never outlive the access that created it. Every pairing minted by this command is bound to its creator via the new `owner` field on pairing records (Sendspin/aiosendspin#329): a guest's pairing is session-scoped (removed two minutes after its client disconnects, when guest access is switched off, and at server startup), while a full user's pairing is durable but removed when their account is deleted or all their sessions are revoked, via a new access-revoked subscription on the auth manager. Pairings made by an operator through the settings flow are untouched by all of this. While this pairing mechanism is more complicated than I initially thought, a simpler (permanent) authentication approach would essentially add a back door into the pairing mechanism, especially coupled with guest mode. A follow up could then also add more verifications like: - checking for implemented roles so only a safe subset is allowed (for example no source role for guest clients) - enforcing that the productName stays the same, effectively disallowing spoofing ## Limitations With this PR, the Web player check (`productName`) is a UX guard, not a security boundary, since the hello is self-declared; the actual boundaries are proof of key possession (the token) and the owner-bound lifetime above. Removing a record can never strand a client: the server falls back to the Sentinel PSK on the next handshake and the frontend re-pairs automatically. **Related issue (if applicable):** - related issue <link to issue> ## Types of changes - [ ] Bugfix (non-breaking change which fixes an issue) — `bugfix` - [x] New feature (non-breaking change which adds functionality) — `new-feature` - [ ] Enhancement to an existing feature — `enhancement` - [ ] New music/player/metadata/plugin provider — `new-provider` - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) — `breaking-change` - [ ] Refactor (no behaviour change) — `refactor` - [ ] Documentation only — `documentation` - [ ] Maintenance / chore — `maintenance` - [ ] CI / workflow change — `ci` - [ ] Dependencies bump — `dependencies` ## Checklist - [x] The code change is tested and works locally. - [x] `pre-commit run --all-files` passes. - [x] `pytest` passes, and tests have been added/updated under `tests/` where applicable. - [ ] For changes to shared models, the companion PR in `music-assistant/models` is linked. - [x] For changes affecting the UI, the companion PR in `music-assistant/frontend` is linked: music-assistant/frontend#2399 - [x] I have read and complied with the project's [AI Policy](https://github.com/music-assistant/.github/blob/main/AI_POLICY.md) for any AI-assisted contributions. - [ ] I have [raised a PR against the documentation repository](https://github.com/music-assistant/music-assistant.io/blob/main/CONTRIBUTING.md) targeting the main or beta branch as appropriate. --------- Co-authored-by: Marcel van der Veldt <[email protected]>