feat(backend): file-scoped content tokens for media URLs
Opening an original by URL (?access_token=) baked in the 15-minute access token, so a long video opened in a new tab stopped streaming once that token expired mid-playback: the access token can't be refreshed in an already-opened tab, and its next Range request 401'd. Add a content token: a signed, single-file capability (typ=content, fid claim) with its own longer TTL (CONTENT_TOKEN_TTL, default 6h) and — crucially — no session id, so it survives refresh rotation and outlives the short access TTL. POST /files/:id/content-token mints one after the same view-ACL check content serving does; GET /files/:id/content now runs under content-aware auth that accepts either a normal access token or a content token scoped to that file. View permission is still enforced against the token's user, so the token only changes when a file may be read by URL, never which files. It's a bearer capability for that one file until expiry, hence the bounded, configurable TTL. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,13 @@ type Config struct {
|
||||
JWTSecret string
|
||||
JWTAccessTTL time.Duration
|
||||
JWTRefreshTTL time.Duration
|
||||
// ContentTokenTTL is how long a content token stays valid. The token is a
|
||||
// single-file capability used to open or stream an original by URL (e.g. a
|
||||
// long video in a new tab); it is deliberately longer-lived than the access
|
||||
// token and independent of the session, so playback survives access-token
|
||||
// expiry and refresh rotation. Keep it only as long as a viewing session
|
||||
// plausibly lasts — it is a bearer credential for that one file until expiry.
|
||||
ContentTokenTTL time.Duration
|
||||
// TrustedProxies lists the reverse-proxy hops (CIDRs or IPs) whose
|
||||
// X-Forwarded-For header is trusted. The auth rate limiter keys on the
|
||||
// client IP, so this must match the proxy in front of the app — otherwise
|
||||
@@ -139,6 +146,8 @@ func Load() (*Config, error) {
|
||||
JWTAccessTTL: parseDuration("JWT_ACCESS_TTL", "15m"),
|
||||
JWTRefreshTTL: parseDuration("JWT_REFRESH_TTL", "720h"),
|
||||
|
||||
ContentTokenTTL: parseDuration("CONTENT_TOKEN_TTL", "6h"),
|
||||
|
||||
TrustedProxies: parseCSV("TRUSTED_PROXIES", "127.0.0.1/32,::1/128,172.16.0.0/12"),
|
||||
|
||||
AdminUsername: defaultStr("ADMIN_USERNAME", "admin"),
|
||||
|
||||
Reference in New Issue
Block a user