From 6fba04cd00592af2b37cd423091f4820b21fe5fa Mon Sep 17 00:00:00 2001 From: Masahiko AMANO Date: Mon, 15 Jun 2026 17:53:30 +0300 Subject: [PATCH] docs(project): document the content-token endpoint and CONTENT_TOKEN_TTL Add POST /files/{file_id}/content-token to the spec, note that the content GET's access_token parameter also accepts a content token, and document the CONTENT_TOKEN_TTL knob (default 6h) and its leak/revocation trade-off. Co-Authored-By: Claude Opus 4.8 --- .env.example | 8 ++++++++ openapi.yaml | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 7d65d5e..272bacd 100644 --- a/.env.example +++ b/.env.example @@ -54,6 +54,14 @@ JWT_SECRET=change-me-to-a-random-32-byte-secret JWT_ACCESS_TTL=15m JWT_REFRESH_TTL=720h +# How long a content token is valid. It's a single-file capability the client +# puts in a media URL to open/stream an original by link (e.g. a long video in a +# new tab), so playback survives the short access-token expiry and session +# rotation. Longer = fewer interruptions but a wider window in which a leaked URL +# can read that one file; it can't be revoked before expiry. Keep it roughly as +# long as a viewing session lasts. +CONTENT_TOKEN_TTL=6h + # Reverse-proxy hops (comma-separated CIDRs/IPs) whose X-Forwarded-For is trusted, # so the auth rate limiter sees real client IPs instead of the proxy's. The default # covers loopback and the Docker bridge ranges a host nginx reaches the container diff --git a/openapi.yaml b/openapi.yaml index 6131772..bb3a8a1 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -350,7 +350,11 @@ paths: required: false schema: type: string - description: Access token, as an alternative to the Authorization header (GET only). + description: > + Access token or a file-scoped content token (obtained from POST + /files/{file_id}/content-token), as an alternative to the + Authorization header (GET only). A content token outlives the + access token, so long media keeps streaming past access-token expiry. responses: '200': description: File binary @@ -392,6 +396,39 @@ paths: schema: $ref: '#/components/schemas/Error' + /files/{file_id}/content-token: + post: + tags: [Files] + summary: Mint a content token for opening/streaming the original by URL + description: > + Returns a short-lived, single-file capability token to place in the + access_token query parameter of GET /files/{file_id}/content. Unlike the + access token it is scoped to this one file and is session-independent, so + it survives access-token expiry and refresh rotation — letting a long + video opened in a new tab keep streaming. Requires view permission on the + file. The token is a bearer credential for that file until it expires. + parameters: + - $ref: '#/components/parameters/file_id' + responses: + '200': + description: Content token + content: + application/json: + schema: + type: object + required: [token, expires_in] + properties: + token: + type: string + description: Capability token for the access_token query parameter. + expires_in: + type: integer + description: Token lifetime in seconds. + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + /files/{file_id}/thumbnail: get: tags: [Files]