Files
parking_solution/wiki/concepts/device-input-flow.md
T
julian 3294f188dd Dingtian input push: HTTP Digest auth + auto-config on assign
Secure the device→backend input push, and configure it automatically when the
admin assigns the device (no manual URL/secret entry).

Auth — HTTP Digest (chosen by hardware testing: the device can't push to a
self-signed HTTPS backend, but does Digest correctly; a URL token is sniffable/
logged):
- digest-auth.ts: MD5 qop=auth challenge/verify, single-use nonces (replay
  resistance). Password never crosses the wire.
- push route: Digest + source-IP allowlist; per-device pushUser/pushPassword from
  lane_devices. Still not behind the SPA cookie/CSRF auth (machine call). The
  signed event log remains the real anti-fraud guarantee.

Auto-config on assign:
- setup assign: for push-capable devices, generate Digest creds, call
  configureInputPush to write them + the push URLs to the device, store the creds
  (password not echoed back). net.ts derives the backend IP on the device's
  subnet (BACKEND_HOST_IP override).
- driver configureInputPush sets auth=2 + creds; PushConfig carries the creds.
- removed the earlier URL-token approach.

Two hard-won device-write bugs fixed in the driver:
- configApi now sets an explicit Content-Length — the device silently ignores
  chunked request bodies (Node's default without Content-Length), so every config
  write looked successful ({"status":0}) but did nothing. This was the root cause
  of the session's "writes don't apply" mystery.
- #writeConfig polls until the change is verified, retrying (the device reboots on
  apply; back-to-back writes were lost). The `pass` field caps at 31 chars, so the
  generated password is 24 hex chars.

Verified on hardware: assign auto-configures the device; all 4 inputs then push
with Digest auth, zero failures. wiki/device-input-flow updated.
2026-06-14 16:39:08 +02:00

4.6 KiB

type, tags, sources, updated
type tags sources updated
concept
parking
architecture
devices
entry-flow
2026-06-15

Device Input Flow (button → backend → relay)

How a physical button press drives the entry lane. The backend is the source of truth: the device only reports the press; the host decides and commands the relay. This is the host-in-the- loop flow the dingtian-relay makes possible (and the uhppote-controller could not).

The path (no polling)

car arrives → driver presses button (input I_N, dry contact to GND)
  → device HTTP-pushes  GET …/api/devices/dingtian/<deviceId>/input/<N>/on
  → backend: emit internal device event (device-events bus)
  → backend entry flow: create + sign an entry event, print the ticket
  → backend: pulseOpen(N) over UDP  → barrier opens
  → (on release) device pushes …/input/<N>/off
  • Push, not poll. The device's input_link_url feature is configured (by the driver's configureInputPush()) to call the backend on each input edge — see dingtian-relay. The driver's poll path remains only as a dev/fallback aid.
  • Per-input path carries the input number in the URL (…/input/3/on), so routing needs no body parsing. Both edges (on/off) are sent.
  • Internal event bus (device-events.ts, a Node EventEmitter) decouples the HTTP/transport layer from business logic — drivers/pushes emit; the entry flow subscribes. Keeps the app device-adapter-pattern.

Trust model (important — flat network, no VLAN)

The relay-control direction (host → device) is unauthenticated UDP, and the site is a flat network with no VLAN (network-isolation is not yet enforceable here). So we do not trust the device or the network. Instead:

  • Every barrier open is a host decision, recorded as a signed event BEFORE the relay fires (append-only-event-chain). If anyone opens the relay out-of-band (which the flat network allows), there is no matching signed event → a detectable anomaly. The anti-fraud guarantee is the signed log, not device/network auth.
  • The inbound push endpoint is intentionally not behind the SPA's cookie/CSRF auth (it's a machine call from the device). It is guarded by HTTP Digest auth + a source-IP allowlist (defence-in-depth), but these are not the security boundary.
  • This sharpens under the autonomous-direction roadmap: with no operator, tamper detection via the signed log matters more than perimeter auth.

Push authentication — Digest (decided by hardware testing)

The secret must not be in the URL (sniffable, logged) and the password must not cross the wire in the clear. We empirically tested the device to pick the strongest achievable option:

Option Device result
HTTPS (self-signed) ❌ device won't push to a self-signed cert
Digest auth (auth=2) ✅ works — full 401-nonce challenge/response
Basic auth ✅ works (but password base64 on the wire)
URL token rejected by design (visible in URL/logs)

→ HTTP Digest (MD5, qop=auth). The password is never sent (only a nonce-keyed hash); nonces are single-use (replay resistance). Per-device credentials (pushUser/pushPassword) are generated by the backend on device assign, written to the device's input_link_url config, and stored in lane_devices — the admin never types a URL or secret. HTTPS would be stronger but the device can't do it here; Digest + the signed log is the practical answer on a flat network. See apps/server/src/digest-auth.ts.

Dingtian config-write gotchas (cost a lot of debugging)

Writing the device's config API (/api/v2/config_set.cgi) has two non-obvious traps — both now handled in the driver:

  1. Content-Length is mandatory. The device's embedded HTTP server does not accept chunked request bodies. Node uses chunked encoding when Content-Length is absent, so the device silently ignores the body and returns {"status":0} anyway — the write looks successful but nothing changes. Always set Content-Length.
  2. The pass field caps at 31 chars (longer is silently truncated → Digest mismatch). The generated push password is 24 hex chars (96 bits).
  3. (Also: the device reboots on apply, so the driver writes then polls until the change is verified, retrying — back-to-back writes onto a rebooting device are lost.)

Status

Input push verified on hardware with Digest auth (all 4 inputs, real presses authenticated, no failures). The entry flow itself (signed event + ticket print + pulseOpen) is the next build — see dingtian-relay.