fix(remote): return session cache for device-code and cache-only logins

Answered login prompts and uploaded cache now count as client auth; the client closes on exit and keeps the remote session alive during long downloads.
This commit is contained in:
imSp4rky
2026-09-16 19:36:31 -06:00
parent aec45e30cc
commit ffda836442
10 changed files with 222 additions and 18 deletions
+3 -3
View File
@@ -839,7 +839,7 @@ Report the install status of the external binaries unshackle depends on (the sam
!!! note "Developer feature"
The `/api/session/*` endpoints power unshackle's **remote download** mode. A thin local client authenticates once against a remote server, then fetches titles, tracks, and segments, and proxies DRM licensing back through it. The server mounts these endpoints in `--remote-only` mode, and they are what the built-in `RemoteService` client drives. Most end users never call them directly.
The server holds remote sessions in memory. They expire after `serve.session_ttl` (default 300s), except while awaiting interactive authentication input, which uses a 600s window. A remote session binds to the IP that made it, so the server rejects a request from a different IP with `403 FORBIDDEN`.
The server holds remote sessions in memory. They expire after `serve.session_ttl` (default 300s) without a request, except while awaiting interactive authentication input, which uses a 600s window. A remote session binds to the IP that made it, so the server rejects a request from a different IP with `403 FORBIDDEN`.
### Remote session lifecycle overview
@@ -874,7 +874,7 @@ The `proxy` value must be a full proxy URI, unless the operator gives your API k
}
```
`server_account` is `true` when the server authenticated with one of its own accounts (`serve.server_accounts`). Otherwise the server uses only what the client sent, and never falls back to its own credentials. Only a client that sent its own cookies or credentials gets cookies, auth headers, and the remote session cache back. For any other remote session (a server account, or an anonymous login through the server's proxy) the tracks and segments responses carry no cookies and no auth headers. The server also drops secret-looking keys from track data, and the cache stays on the server. A server-account service also refuses a client proxy URI, because the server's login would pass through it.
`server_account` is `true` when the server authenticated with one of its own accounts (`serve.server_accounts`). Otherwise the server uses only what the client sent, and never falls back to its own credentials. Only a client that sent its own cookies, credentials, or cache files gets cookies, auth headers, and the remote session cache back. A client that answered a login prompt (a device code or an OTP) gets them too. This applies only when the login then succeeds and the remote session does not use a server account. For any other remote session (a server account, or an anonymous login through the server's proxy) the tracks and segments responses carry no cookies and no auth headers. The server also drops secret-looking keys from track data, and the cache stays on the server. A server-account service also refuses a client proxy URI, because the server's login would pass through it.
| Status | Error code | Meaning |
| --- | --- | --- |
@@ -1134,7 +1134,7 @@ Tear down a remote session. The server cancels its input bridge, harvests any up
}
```
The `cache` field is present only if the remote session produced cache files. `404 SESSION_NOT_FOUND` if the remote session does not exist.
The `cache` field is present only if the remote session produced cache files and the login belongs to the client. `404 SESSION_NOT_FOUND` if the remote session does not exist.
---
+14 -7
View File
@@ -195,10 +195,16 @@ hood the client walks a remote session through its lifecycle.
=== "5. Download + close"
The client downloads, decrypts, and muxes locally. On completion it deletes
the remote session. If the server has updated cache files (for example a refreshed
token), the delete returns them and the client saves them locally, so the **next**
remote session can skip interactive auth.
The client downloads, decrypts, and muxes locally. While it works, it sends a
keep-alive request to the remote session at an interval of one third of
`session_ttl`, so a long download does not let the remote session expire. On
completion, and also when `dl` exits early (`--list`, Ctrl+C, an error), it
deletes the remote session. If the login belongs to the client, the `DELETE`
request returns the updated cache files (for example a refreshed token) and the
client saves them locally, so the **next** remote session can skip interactive
authentication. The login belongs to the client when the client sent
cookies, credentials, or cache files, or answered a login prompt (a device code
or an OTP) that led to a successful login. A server-account login never does.
!!! tip "Renaming remote titles locally"
You can rename titles for a remote service you do not have installed locally by
@@ -423,9 +429,10 @@ _sessions/<pbkdf2_hmac(sha256, X-Secret-Key, "unshackle-session-ns", 100000)[:12
```
The handler writes forwarded `cache` files into that directory before authentication.
On `DELETE`, the handler harvests updated cache files (compressing each with zlib
and base64-encoding, **excluding** `titles_*` files) and returns them under a
`cache` field, so the client can keep refreshed tokens:
On `DELETE`, when the login belongs to the client (`client_auth`), the handler
harvests updated cache files (compressing each with zlib and base64-encoding,
**excluding** `titles_*` files) and returns them under a `cache` field, so the
client can keep refreshed tokens:
```json
{ "status": "ok", "cache": { "tokens": "...base64(zlib(bytes))..." } }