File serving controller with signed URL support.
Handles secure file retrieval with token-based authentication and cache headers.
Summary
Functions
Authorizes a file-info read. The owner (file.user_uuid) or an Owner/Admin may
read; everyone else — and a missing file — is {:error, :not_found}, the
same result, so the endpoint is not a file-existence oracle.
Get file information without serving the file.
Requires an authenticated caller for the info endpoint. It lives in the
unauthenticated [:browser, :phoenix_kit_auto_setup] scope, so this is the
gate: before it, any visitor got file metadata and a freshly-minted signed URL
for any uuid, which handed out capability URLs the signing scheme exists to
withhold (issue #687 class).
Serve the DZI manifest for an image, generating it lazily if it doesn't exist yet.
Serve a single DZI tile, generating it lazily if it doesn't exist yet.
Serve a file variant by ID with signed URL token.
Functions
@spec authorize_file_read(term(), PhoenixKit.Users.Auth.User.t()) :: {:ok, map()} | {:error, :not_found}
Authorizes a file-info read. The owner (file.user_uuid) or an Owner/Admin may
read; everyone else — and a missing file — is {:error, :not_found}, the
same result, so the endpoint is not a file-existence oracle.
The staff bypass is Scope.system_role?/1 (Owner/Admin), NOT
can_access_admin_area?/1: a holder of a single module permission must not be
able to read every other user's file metadata and signed variant URLs.
Get file information without serving the file.
Request
GET /api/files/:file_uuid/infoResponse
Success (200):
{
"file_uuid": "uuid",
"original_filename": "photo.jpg",
"mime_type": "image/jpeg",
"file_type": "image",
"size": 1234567,
"status": "active",
"variants": [
{
"variant_name": "original",
"mime_type": "image/jpeg",
"size": 1234567,
"width": 1920,
"height": 1080,
"url": "/file/uuid/original/token"
}
]
}
@spec require_user(term()) :: {:ok, PhoenixKit.Users.Auth.User.t()} | {:error, :no_user}
Requires an authenticated caller for the info endpoint. It lives in the
unauthenticated [:browser, :phoenix_kit_auto_setup] scope, so this is the
gate: before it, any visitor got file metadata and a freshly-minted signed URL
for any uuid, which handed out capability URLs the signing scheme exists to
withhold (issue #687 class).
Serve the DZI manifest for an image, generating it lazily if it doesn't exist yet.
Request
GET /tiles/:token/:dzi_filenamewhere dzi_filename is "<file_uuid>.dzi" and token is the signed
per-file token from URLSigner.generate_token(file_uuid, "dzi").
Returns the XML manifest describing the image's dimensions and tile
config — Tessera's generate_manifest/3 produces it on first request,
subsequent requests serve from storage.
The token gates BOTH manifest and tile generation: without it, the
endpoint is a 404. The MediaBrowser emits manifest URLs only when
storage_tile_generation_enabled is on, so unauthenticated callers
can't trigger lazy ImageMagick work by guessing UUIDs.
Serve a single DZI tile, generating it lazily if it doesn't exist yet.
Request
GET /tiles/:token/:files_segment/:level/:tile_filenamewhere token is the signed per-file token (same one used by
serve_manifest/2), files_segment is "<file_uuid>_files",
level is the integer zoom level, and tile_filename is
"<col>_<row>.<ext>". This matches the layout Tessera writes to
storage and the URL convention OpenSeadragon derives from a DZI
manifest's base URL (token in the path survives that derivation;
query-string tokens don't).
Serve a file variant by ID with signed URL token.
Request
GET /file/:file_uuid/:variant/:tokenParameters
file_uuid: UUID of the filevariant: Variant name (e.g., "original", "thumbnail", "medium")token: Signed token for authentication
Response
Success (200):
- File streamed to client with appropriate headers:
Cache-Control: public, max-age=31536000, immutable(1 year)ETag: "md5-hash"Content-Type: <mime-type>Content-Disposition: inline; filename="..."
Not Modified (304):
- Returned when request includes
If-None-Matchmatching the file ETag
Error (401):
"Invalid or expired token"Error (404):
"File or variant not found"