feat(video): short-lived signed URLs for original-quality playback #168

Merged
cameron merged 1 commits from feat/signed-original-video into master 2026-10-14 01:55:34 +00:00
Owner

Summary

Opt-in original-quality playback for clients whose player can't send an Authorization header. HLS stays the default.

sequenceDiagram
    participant App
    participant ImageApi
    App->>ImageApi: POST /video/original {path, library} (Bearer)
    ImageApi-->>App: {url: /video/original/stream?token=…, expires_at}
    App->>ImageApi: GET url (Range: bytes=…) — no auth header
    ImageApi-->>App: 206 original bytes
token = HS256 JWT {sub, lib, path, exp = now + 6h}
        key = SECRET_KEY + ":video-original"   # login JWTs can't be replayed here, or vice versa
stream: verify token → library by id → is_valid_full_path(root, path) → NamedFile (Range, ETag)

generate_video's library fallback moved into resolve_request_path so both handlers share it. CLAUDE.md endpoint list updated (its /video/stream?path= entry was stale).

Evidence

  • Before: no way to hand a native player the original bytes without exposing the login JWT in a URL.
    After:
    original_video_url_streams_with_range_and_no_auth_header   mint → GET with Range: bytes=2-5 → 206 "2345"
    original_video_url_rejects_non_video_and_unauthenticated   .txt → 400, no Bearer → 401
    stream_original_video_rejects_bad_tokens                   login JWT → 401, "../../etc/passwd" token → 404
    original_url: round_trip / tampered / expired / keys_not_interchangeable   ok
    
    Full suite DATABASE_URL=":memory:" cargo test: 1,531 passed (one run hung in otel_log_correlation; it passes repeatedly on both master and this branch, so it looks like an existing flake). Clippy + fmt clean.

Merge Danger

Door: two-way

Additive endpoints; nothing calls them until a client opts in.

Blast Radius: small

A minted URL grants unauthenticated read of that one video for 6 hours. Rotating SECRET_KEY revokes all of them (along with login tokens).

🤖 Generated with Claude Code

## Summary Opt-in original-quality playback for clients whose player can't send an `Authorization` header. HLS stays the default. ```mermaid sequenceDiagram participant App participant ImageApi App->>ImageApi: POST /video/original {path, library} (Bearer) ImageApi-->>App: {url: /video/original/stream?token=…, expires_at} App->>ImageApi: GET url (Range: bytes=…) — no auth header ImageApi-->>App: 206 original bytes ``` ```text token = HS256 JWT {sub, lib, path, exp = now + 6h} key = SECRET_KEY + ":video-original" # login JWTs can't be replayed here, or vice versa stream: verify token → library by id → is_valid_full_path(root, path) → NamedFile (Range, ETag) ``` `generate_video`'s library fallback moved into `resolve_request_path` so both handlers share it. CLAUDE.md endpoint list updated (its `/video/stream?path=` entry was stale). ## Evidence - **Before:** no way to hand a native player the original bytes without exposing the login JWT in a URL. **After:** ```text original_video_url_streams_with_range_and_no_auth_header mint → GET with Range: bytes=2-5 → 206 "2345" original_video_url_rejects_non_video_and_unauthenticated .txt → 400, no Bearer → 401 stream_original_video_rejects_bad_tokens login JWT → 401, "../../etc/passwd" token → 404 original_url: round_trip / tampered / expired / keys_not_interchangeable ok ``` Full suite `DATABASE_URL=":memory:" cargo test`: 1,531 passed (one run hung in `otel_log_correlation`; it passes repeatedly on both master and this branch, so it looks like an existing flake). Clippy + fmt clean. ## Merge Danger **Door:** two-way Additive endpoints; nothing calls them until a client opts in. **Blast Radius:** small A minted URL grants unauthenticated read of that one video for 6 hours. Rotating `SECRET_KEY` revokes all of them (along with login tokens). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
cameron added 1 commit 2026-10-14 00:42:51 +00:00
POST /video/original mints a URL for one video's original bytes, signed
with a key derived from SECRET_KEY (distinct from the login-token key)
and valid for 6 hours. GET /video/original/stream?token= serves it via
NamedFile, so Range/seek works, without an Authorization header — native
players can use it directly. HLS stays the default path.

Also extracts generate_video's library fallback into resolve_request_path
and fixes the stale /video/stream entry in CLAUDE.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cameron merged commit 40d9dcdbb6 into master 2026-10-14 01:55:34 +00:00
cameron deleted branch feat/signed-original-video 2026-10-14 01:55:35 +00:00
Sign in to join this conversation.