Device security and robustness (openpanel C0): check code, pnc, lenient report

- POST /devices/pair answers a 2-character `check`, listed by /devices/pending
  and shown beside each waiting entry; five codes a minute per address, and at
  50 waiting the oldest gives way instead of a 429
- portal-minted device tokens are asked for with the device's nonce (`pnc`) and
  held against it, so unpair and delete revoke them; one without `pnc` stands
  only while the nonce is 0
- a device credential acts as the device (`device_principal`), not as the
  approving superuser, so deleting that account unpairs nothing
- the report is validated port by port: unknown enum values read as `unknown`,
  unreadable ports and sections are left out and logged, `config` may name an
  entry of `config_schemas`; 422 only for no schema 1 or no object
- devices.json is parsed once per change (cached by stat), delete drops the
  report and keeps the nonce, a busy recheck runs in the background
- scope and io only from enabled flows (start/stop tells the devices), a port
  bound twice gives one io row
- the Devices tab's saves cancel and hold back device refetches until settled
This commit is contained in:
Melvin Strobl
2026-10-16 19:26:05 +02:00
parent 13d23f6baf
commit c604cfdf3b
21 changed files with 773 additions and 119 deletions
+4 -4
View File
@@ -192,13 +192,13 @@ on its own, so this is for reclaiming space sooner.
| `POST` | `/dashboards/{name}/duplicate` | copy into an unpublished `{name}_copy` |
| `GET` | `/devices/` | list devices, with `online`, `last_seen`, `report` and `reported_at` |
| `POST`, `GET`, `PUT`, `DELETE` | `/devices/{id}` | create, read, save or delete one; a save sends the `version` it read and gets 409 when someone saved first. A read adds `io` and `bindings`, its ports as the device nodes of published flows wire them |
| `POST` | `/devices/pair` | start device pairing, optionally with `{name, board, firmware}`; unauthenticated |
| `POST` | `/devices/pair` | start device pairing, optionally with `{name, board, firmware}`; answers `code`, `secret` and the two-character `check` the device shows beside its code; unauthenticated, five a minute per address, the oldest of 50 waiting requests gives way |
| `GET` | `/devices/pair/{code}` | poll pairing with its `secret`; answers `device` (and `panel` for older firmware); unauthenticated |
| `GET` | `/devices/pending` | devices waiting for a code, as they describe themselves |
| `GET` | `/devices/pending` | devices waiting for a code, as they describe themselves, each with its `check` |
| `GET` | `/devices/pair/{code}/device` | what is waiting on a code, before it is approved |
| `POST` | `/devices/{id}/pair` | adopt by `{code}`, or by `{request}` from the pending list for a request from your network |
| `POST` | `/devices/{id}/unpair` | revoke the device credential |
| `PUT` | `/devices/{id}/report` | what the device has, from its own credential only; at most 16 KB, replaces the last report |
| `POST` | `/devices/{id}/unpair` | revoke the device credential, one the portal issued included |
| `PUT` | `/devices/{id}/report` | what the device has, from its own credential only; at most 16 KB, replaces the last report, read leniently (see [What a device reports](../interface/dashboards.md#what-a-device-reports)) |
`/panels/pair`, `/panels/pair/{code}` and `GET /panels/{id}` answer as their
`/devices/` counterparts, for firmware that has not updated yet.