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:
@@ -91,8 +91,9 @@ func NewRouter(
|
||||
files.PATCH("/:id", fileHandler.UpdateMeta)
|
||||
files.DELETE("/:id", fileHandler.SoftDelete)
|
||||
|
||||
files.GET("/:id/content", fileHandler.GetContent)
|
||||
files.PUT("/:id/content", fileHandler.ReplaceContent)
|
||||
// Mints a content token (strict auth) for the GET /:id/content route below.
|
||||
files.POST("/:id/content-token", fileHandler.CreateContentToken)
|
||||
files.GET("/:id/thumbnail", fileHandler.GetThumbnail)
|
||||
files.GET("/:id/preview", fileHandler.GetPreview)
|
||||
files.POST("/:id/views", fileHandler.RecordView)
|
||||
@@ -106,6 +107,15 @@ func NewRouter(
|
||||
files.DELETE("/:id/tags/:tag_id", tagHandler.FileRemoveTag)
|
||||
}
|
||||
|
||||
// Serving an original is the one read that can outlive a 15-minute access
|
||||
// token — a long video streams via repeated Range requests over many minutes.
|
||||
// So this route alone also accepts a file-scoped content token (see
|
||||
// HandleContent), letting the media URL stay valid for the whole playback.
|
||||
media := v1.Group("/files", auth.HandleContent())
|
||||
{
|
||||
media.GET("/:id/content", fileHandler.GetContent)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Tags (all require auth)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user