A configured PHOTOPRISM_DOWNLOAD_TOKEN was delivered to every client, so download
requests could not be attributed to the session that made them. Unlike preview
tokens, a download token is a query parameter on a one-shot transfer rather than
part of the URL path, so it is not part of the cache key and a per-session value
costs no cache sharing. There is accordingly no reason to trade the session
binding for it.
Sessions now always receive a signed, session-bound token. The configured value
is still accepted, so permanent URLs keep working, and it is delivered only to
callers with no session to sign for.
No value is generated when the option is blank either, so an instance that was
never configured for permanent URLs has none to accept: Config.DownloadToken
returns an empty string rather than a random one, and tokens.DownloadStatic is
no longer needed.
A session without its own preview token now receives no download token, in both
ClientSession and AddTokenHeaders, since the download token is the higher-value
credential — it also authorizes originals, and a coarse one is accepted for
previews.
Corrects the download-token and download-token-maxage usage strings and the
startup notice, which all described the previous delivery policy, and notes on
the frontend download helper that its anchor path cannot observe an expired
token.
Rework download tokens into stateless, session-scoped signed tokens
(bunny.net Advanced HMAC-SHA256), with a single coarse instance token and
a public placeholder as fallbacks; the "?t=" API contract is unchanged, so
clients keep working. One shared HMAC key signs every URL-token kind,
persisted at config/keys/signing.key (never backed up, always kept in
memory so signing never returns an empty key). Per-session/user download
tokens and their lookup map are removed; the DB columns are kept for safe
downgrades.
The Portal authorizes downloads with its cluster JWT in a request header
(JWT-only, requiring access to all files); every other client presents a
"?t=" token. AuthDownload merges the token gate and session lookup and
audits denials centrally.
Album ZIP downloads select files the same way multi-file downloads do, so
the Sidecar, Originals, and RAW options behave consistently (real sidecar
files included when Sidecar is on). Archived and hidden pictures are always
excluded; private pictures follow the requesting session's scope and are
excluded outright for an unidentified requester.