Bound a panel credential to its own widgets, and let one screen be re-paired

Three things a paired wall panel needed.

The scope check now walks the panel's widgets instead of allowing the
`/messages/` prefix wholesale: a screen may publish what its own controls and
querying charts point at, read the history of what its tiles draw, and nothing
else — the catalogue of every message in the installation included. The same
walk that already bounds its socket, so both surfaces agree.

Pending pairing codes moved out of the per-process dictionary into Redis, keyed
per code with the code's own TTL and indexed in a zset so the fifty-code cap
means the same thing to every worker. Without a Redis there is one process by
definition, and the dictionary stays.

And a per-panel nonce in the token, bumped by `POST /panels/{id}/unpair`: that
refuses the screen hanging there without touching the panel, its dashboards or
their arrangement. A save cannot write the nonce back, so a stale client cannot
undo a revocation. Only for a credential this installation signed — one the
portal minted carries no nonce and is revoked at the hub.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018tULRZJUkZsw7rMJ3h4xvu
This commit is contained in:
2026-08-22 11:57:37 +02:00
co-authored by Claude Opus 5
parent 16ba11fb07
commit 6d84316ce5
7 changed files with 504 additions and 67 deletions
+1
View File
@@ -146,6 +146,7 @@ code normally reaches these through `fluksio.save_artifact` /
| `GET` `PUT` | `/panels/` | which device shows which dashboards |
| `POST` | `/panels/pair` | start a pairing |
| `GET` | `/panels/pair/{code}` | what is holding a code |
| `POST` | `/panels/{id}/unpair` | drop this panel's credential, keep the panel |
## Secrets, modules, alerts
+12 -5
View File
@@ -100,13 +100,18 @@ A wall tablet has no keyboard, so it pairs.
up within a few seconds and never asks again.
What the screen holds is not a login. It reaches that panel's published
dashboards and the message endpoints its widgets speak, and nothing else.
dashboards and the messages its own widgets read or publish, and nothing else
a message no tile on it draws is refused in both directions.
It cannot be made strictly read-only, and that is honest rather than an
oversight: a querying chart publishes its request, and a control on a panel is
the reason you put one there. Deleting the panel revokes the credential, which
is also how you retire a device — the screen falls back to asking for a new
code.
the reason you put one there.
Deleting the panel revokes the credential and the assignment together, which is
how you retire a device and what it showed. Unpairing revokes only the
credential: the panel, its dashboards and their arrangement stay exactly where
they are, and the screen falls back to asking for a new code — which is how you
swap the device out without rebuilding what hangs there.
!!! note "If the link is wrong"
@@ -128,7 +133,9 @@ The portal names the panel and nothing else. What the panel may read is decided
on the installation, on every call, by the same check a locally paired screen
passes. Two differences: it acts as the account the installation was enrolled
with rather than as whoever approved it, and deleting the panel stops it here
immediately while the portal's copy of the token expires on its own.
immediately while the portal's copy of the token expires on its own — which is
also why unpairing, which works on a credential this installation signed, does
not reach a remote screen. Revoke that one at the hub.
## See also