Extend the access token to every endpoint, not just the web UI
Build and push server image / build-and-push (push) Successful in 33s

The management token only gated / and /api/* -- every device-facing
/frame/* endpoint (including /frame/image, which serves the actual
photo bytes) stayed open regardless. That was fine while the server
was assumed LAN-only, but defeats the point now that HTTPS exists
specifically to let this sit behind a public hostname.

build_url() (frame_client.c) is the one chokepoint all firmware-side
URL construction already went through, so it now appends ?token= to
every request it builds -- device fetches and QR-embedded links alike
-- instead of that being bolted on per-callsite. Server-side, the
former require_management_token dependency (renamed require_access_token)
is applied to /frame/config, /frame/image, /frame/advance,
/frame/photo-info, /frame/face-labels, and /frame/share/{asset_id} too.
/health stays open -- pure liveness, nothing sensitive to protect.
This commit is contained in:
2026-07-19 09:52:01 -04:00
parent 6c7468a36e
commit 62cf907d88
4 changed files with 109 additions and 81 deletions
+20 -16
View File
@@ -33,13 +33,17 @@ algorithm itself -- it just streams the response straight to the panel.
for HTTPS, put a TLS-terminating reverse proxy (e.g. nginx) in front of
it and enter the proxy's `https://` address instead (see
`firmware/README.md`'s HTTPS section for what the ESP32 side needs).
6. **Optional: set `MANAGEMENT_TOKEN`** in `docker-compose.yml` to gate the
web UI behind a shared secret (leave unset to keep it open, the
previous default -- fine on a trusted LAN). If set, paste the same
value into the ESP32's captive portal setup form's **Access Token**
field so the manage-menu's "scan to manage" QR code embeds it
automatically (`?token=...`); visiting the page without a valid token
in the URL shows a plain token-entry prompt instead of the config UI.
6. **Optional: set `MANAGEMENT_TOKEN`** in `docker-compose.yml` to gate
the *entire server* -- the web UI (`/`, `/api/*`) and every
device-facing `/frame/*` endpoint -- behind a shared secret (leave
unset to keep it all open, the previous default -- fine on a trusted
LAN). If set, paste the same value into the ESP32's captive portal
setup form's **Access Token** field: the device then sends it on
every request it makes, and the manage-menu/share QR codes embed it
automatically (`?token=...`) so scanning them just works. Visiting
the web UI without a valid token in the URL shows a plain token-entry
prompt instead of the config UI; `/health` stays open regardless
(pure liveness, nothing sensitive in it).
## Endpoints
@@ -116,15 +120,15 @@ algorithm itself -- it just streams the response straight to the panel.
in sequential or shuffle order per the Order setting. Dragging photos
in the web UI (or using "Show next") only rearranges what's already in
that lookahead; it doesn't add or remove photos from the album.
- `/frame/image`, `/frame/advance`, `/frame/photo-info`, `/frame/face-labels`,
and `/frame/share/{asset_id}` -- the device-facing endpoints -- aren't
authenticated. That's fine on a trusted home LAN for now, but worth
revisiting if this ever needs to sit somewhere less trusted.
`/frame/share` at least is scoped to only ever create a link for a
photo this frame is actually showing or has queued, not any Immich
asset ID someone might guess. The web UI (`/`, `/api/*`) is separately
gated by `MANAGEMENT_TOKEN` if set (see Setup above) -- these are two
independent trust boundaries, not one shared mechanism.
- Every endpoint except `/` and `/health` -- the web UI's `/api/*` and
every device-facing `/frame/*` -- requires `?token=` (or the
`mgmt_token` cookie the web UI sets after a valid one) once
`MANAGEMENT_TOKEN` is set (see Setup above); unset, everything stays
open like before, which is still fine on a trusted home LAN. `/frame/share`
additionally stays scoped to only ever create a link for a photo this
frame is actually showing or has queued, not any Immich asset ID
someone might guess -- a second layer a leaked token alone wouldn't
bypass.
- The 6-color palette RGB values in `app/image_pipeline.py` are
approximations, not measured values (Waveshare doesn't publish exact
color primaries for this panel) -- tune them once you can compare a