3 Commits
Author SHA1 Message Date
maziggy 258db95483 fix(overlay): authenticate the OBS overlay with a token when login is enabled (#2613)
The /overlay/{id} route renders without a login, but everything it draws is
auth-gated: printer status and name (PRINTERS_READ), one setting (SETTINGS_READ),
and the camera stream (a camera-stream token). A signed-in browser rides its JWT
from local storage; OBS is a fresh browser with no session, so the overlay stayed
blank whenever authentication was enabled. Cloudflare/remote access was never the
cause -- an incognito window fails identically.

Give the overlay a self-contained kiosk-token mode, mirroring the Cam Wall:

- New `overlay` long-lived-token scope, kept separate from `camwall`: the overlay
  names the printed file on screen, which a Cam Wall token is trusted never to
  expose, so folding it in would silently widen every existing wall token.
- New token-authed GET /printers/{id}/overlay-status returning exactly the fields
  the overlay draws and nothing else; added to the auth-middleware allowlist so it
  reaches its own RequireOverlayTokenIfAuthEnabled gate.
- StreamOverlayPage reads ?token= and, in that mode, authenticates its status and
  camera calls with the token and skips the WebSocket (the 2s poll is the feed).
  The logged-in path is unchanged.
- Token-mint UI (Settings > API Keys) offers the scope with a ready-made
  /overlay/{id}?token= URL copied once on creation.
2026-07-20 13:04:35 +02:00
maziggy d09db436c3 feat(camwall): serve the Cam Wall at /camwall, and on a token-authenticated kiosk
Cam Wall had no URL — the only way in was the toggle on the Printers page,
so it could not be bookmarked, linked, or shown on a wall-mounted screen.

Add a standalone /camwall route. Signed in, it is the wall as it was. For a
TV or Pi with no login, it authenticates with a long-lived token in the URL.

A kiosk needs the printer list and per-printer status, both of which sit
behind PRINTERS_READ. Rather than widen camera_stream to cover GET /printers
— whose response carries serial_number and ip_address, which have no business
on a screen in a shared room — add a read-only feed at
GET /api/v1/camwall/printers that serves only what a tile draws, and gate it
on a new camwall token scope. The print filename is not served at all: a token
wall renders the compact overlay, so the part on the bed is never named.

The scope is separate rather than a widening: camera_stream tokens are already
in the wild, minted to hand out video, and must not gain the ability to
enumerate a fleet by name. camera_stream is refused by the feed; camwall
passes the stream gate so its own tiles fill.

Kiosk walls drop the settings popover and click-through entirely (not merely
hidden — a passive screen must carry no focusable control it cannot act on),
cap the overlay at compact, and poll rather than open a WebSocket. maxLive,
interval and status can be set from the URL, clamped to the popover's ranges.
2026-07-11 13:38:15 +02:00
maziggy fcda728af4 feat(#1108): long-lived camera-stream tokens + fix(#1089) audit-pass tweaks
#1108 — Long-lived camera-stream tokens for HA / Frigate / kiosks. Camera-only
  V1, hard 365-day cap (no infinite tokens), pbkdf2 hashed at rest, plaintext
  shown to user exactly once on creation. New "Camera API Tokens" panel under
  Settings → API Keys with self-service create/revoke, styled confirm modal,
  admin "All users" view for leak triage. Auth path: /camera/stream tries the
  existing 60-min ephemeral table first, falls through to the long-lived path.
  Indexed lookup_prefix keeps verify O(1) per token.

  Permission audit: gated the existing API-keys-CRUD + Webhook docs + API
  Browser content behind api_keys:read so non-admins with camera:view land on
  the API Keys tab and see only the Camera Tokens panel they actually have
  permission to use. Grid layout collapses to single column for non-admins.

  Tests: 29 new backend (15 service + 14 integration covering create/list/
  revoke ownership rules, the auth fall-through, scope enforcement, prefix
  collisions) + 6 new frontend tests for the section UI including the new
  modal flow. All 77 backend tests + 21 frontend camera tests pass. Ruff
  clean (lint + format).

  Docs: README updated with fan-out + long-lived-token bullets. Wiki gets a
  new "Long-Lived Camera Tokens" section under features/camera.md (HA YAML
  example, security model, permission requirements, revoke flow). Website
  features.html gets the bullet under Camera Streaming.

  Also includes #1089 follow-up tweaks already merged in this branch:
  _stream_start_times.setdefault for accurate stream_uptime, subscribe()
  RuntimeError retry to close the grace-vs-subscribe race, atomic
  unsubscribe count via the iter_subscriber on_unsubscribe callback.
2026-04-25 10:44:37 +02:00