feat(remote): accept serve.users keys in api-only mode and add client auth-header fallback

api-only/remote-only serve now authenticates every serve.users key (str-coerced), not just api_secret, so per-user service allowlists apply. RemoteClient tries X-Secret-Key then X-Api-Key on 401 (sticky winner); remote_services.auth_headers prepends extra header names to the defaults.
This commit is contained in:
imSp4rky
2026-08-12 17:58:55 -06:00
parent 274b2fc308
commit 5796729bf3
8 changed files with 231 additions and 20 deletions
+1 -1
View File
@@ -299,7 +299,7 @@ With `--caddy`, unshackle also starts `caddy run` using the `Caddyfile` located
The header and config are identical between modes, but the code that enforces them differs:
- **API-only server** (`--api-only`, or `--remote-only` which forces API-only). An `api_key_authentication` middleware runs on every request. It lets `GET /api/health` through unconditionally, then reads `X-Secret-Key`. An empty header returns the *"Secret Key is Empty."* body; a header that is not a configured user returns *"Secret Key is Invalid."*. In this mode the accepted-keys map contains exactly one entry: your `api_secret` (labelled `api_user`, with no devices). With `--no-key`, this middleware is simply not installed and the users map is empty.
- **API-only server** (`--api-only`, or `--remote-only` which forces API-only). An `api_key_authentication` middleware runs on every request. It lets `GET /api/health` through unconditionally, then reads `X-Secret-Key`. An empty header returns the *"Secret Key is Empty."* body; a header that is not a configured user returns *"Secret Key is Invalid."*. In this mode the accepted-keys map holds your `api_secret` (labelled `api_user`, with no devices) plus every key under `serve.users`, so a per-user key authenticates here exactly as it does on the integrated server, and that user's `services` allowlist applies as usual. With `--no-key`, this middleware is simply not installed and the users map is empty.
- **Integrated server** (default). A `serve_authentication` middleware wraps pywidevine's authentication (and pyplayready's for `/playready*` paths), and the REST routes live in the same application, guarded by the same check. Here the accepted-keys map is your full `serve` config: `api_secret` (auto-added with access to all loaded devices) plus every entry under `users`, each with its declared `devices` / `playready_devices`.
+12 -3
View File
@@ -82,9 +82,18 @@ keep auth tokens in localStorage rather than in HTTP cookies need. Extraction is
Definitions of remote unshackle service servers, used by the `--remote` mode. Each entry is
named by you (pick it with `--server`, or omit that flag when only one is configured) and gives
the server's `url` (required), an optional `api_key` sent as `X-Secret-Key`, an optional
`server_cdm` boolean, and an optional `services` sub-dict of per-service local overrides such
as `title_map`. In `--remote` mode unshackle turns the server's service list into synthetic CLI
the server's `url` (required), an optional `api_key`, an optional `auth_headers` list, an
optional `server_cdm` boolean, and an optional `services` sub-dict of per-service local
overrides such as `title_map`.
`auth_headers` lists extra header names to send the API key in, tried before the defaults
`X-Secret-Key` and `X-Api-Key`, which are always appended as fallbacks. unshackle sends the
first name; if the server answers `401`, it retries the same request with the next name, and
keeps the one that works for the rest of the session. Names you list keep your spelling and are
not repeated in the fallbacks, so `auth_headers: ["Authorization", "x-secret-key"]` is tried as
`Authorization`, `x-secret-key`, `X-Api-Key`.
In `--remote` mode unshackle turns the server's service list into synthetic CLI
commands that run against it, falling back to the tags in that `services` sub-dict when the
list cannot be fetched. See [remote sessions](../../dev/rest-api/remote-sessions.md) for the
full setup.