Skip to content

feat(basevideo): shared-token auth with browser login page - #799

Open
thusser wants to merge 1 commit into
developfrom
feat/basevideo-http-token-auth
Open

feat(basevideo): shared-token auth with browser login page#799
thusser wants to merge 1 commit into
developfrom
feat/basevideo-http-token-auth

Conversation

@thusser

@thusser thusser commented Aug 22, 2026

Copy link
Copy Markdown
Member

Implements specs/plans/2026-08-21-basevideo-http-token-auth.md (server-side half).

What

Adds an opt-in token: str | None = None parameter to BaseVideo (pyobs/modules/camera/basevideo.py). When set, all content endpoints (/, /video.mjpg, /video.raw, /{filename}) require either Authorization: Bearer <token> or a valid HMAC-signed session cookie:

  • _check_auth(request) — same contract as HttpFileCache._check_auth: no-op without a token, otherwise 401 unless Bearer header or cookie verifies; constant-time compares (hmac.compare_digest) throughout.
  • Cookie is stateless: "<expiry_ts>.<hex HMAC-SHA256(key=token, msg=str(expiry_ts))>", 24 h lifetime, HttpOnly, SameSite=Lax, path=/. Rotating the token invalidates all cookies at once.
  • /login (GET form + POST verify, 303 + cookie on success, 401 after a short delay on failure) and /logout (clears cookie, 303 → /login) — registered only when a token is configured.
  • web_handler translates the 401 into 303 → /login so a browser landing on / reaches the login form; the streaming handlers check auth before response.prepare(), and raw_handler before activate_camera() (unauthenticated requests never wake the camera). /ping stays open.

HttpFile gains a public read-only headers property (pyobs/vfs/httpfile.py) so consumers don't reach into _headers.

Tests

tests/modules/camera/test_basevideo.py (unit style, no TestClient): route gating, 401/303 behavior per endpoint, Bearer accept/reject, login POST success/failure, cookie accept / tampered / expired / cross-token rejection, logout clearing, raw_handler not activating the camera on bad credentials, 401 raised before StreamResponse.prepare(). tests/vfs/test_httpfile.py covers the headers property. token=None default behavior unchanged (existing tests untouched).

⚠️ Coupling — land both halves together

  • This PR (pyobs-core): server-side implementation + HttpFile.headers.
  • Companion PR pyobs/pyobs-gui (same feature branch name, one consumer change): VideoWidget now sends the Authorization header on its raw-socket MJPEG GET.

With token set, the GUI live view breaks until the pyobs-gui half lands — merge both PRs together.

Out of scope (per plan): CORS for BaseVideo endpoints, per-user accounts/Keycloak, other unauthenticated HTTP servers, browser page features beyond the login form.

Add a token: str | None = None parameter to BaseVideo. When set, the
MJPEG/raw/FITS endpoints require either Authorization: Bearer <token> or
an HMAC-signed session cookie issued by a new /login page (/logout clears
it); /ping stays open. Cookie is stateless (expiry + HMAC-SHA256 over the
expiry, keyed by the token), constant-time compares throughout, routes
gated on the token. web_handler translates a 401 into a 303 to /login so
browsers land on the form; stream handlers check auth before prepare()
and raw_handler before activate_camera().

HttpFile gains a public headers property so consumers (pyobs-gui
VideoWidget, separate PR) can read the Authorization header without
reaching into _headers.

Implements specs/plans/2026-08-21-basevideo-http-token-auth.md.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant