Harden Dingtian: authenticated binary relay + disable unused channels

Lock down the relay device for the flat (no-VLAN) network.

Relay control:
- pulseOpen/setRelay now use the Dingtian BINARY protocol (:60000) with a
  relay password — the only relay option with auth (string :60001 has none, and
  is kept only for the read-only status query). Frame verified on hardware.

HardenableDevice capability (driver harden()):
- set a random relay_pw (1-9999); disable unused channels (rs485/can/tcp x2/mqtt
  -> p:255), keeping UDP1 binary (control) + UDP2 string (status).
- write-verified (device reboots on apply).

Assign/Save flow now does: fix preconditions -> harden -> set up input push;
the relay password is stored in lane_devices so the runtime device can command
the relay.

DELIBERATELY NOT touching the device's HTTP CGI session check (session_en):
enabling it on this firmware breaks the config-READ API (ECONNRESET) and locked
the backend out — required a factory reset to recover. The open CGI API is
accepted as flat-network reality; the signed event log is the real guarantee.

Verified end to end on hardware: assign hardens + configures the device, config
API stays reachable, pulseOpen with the stored password fires the relay, without
it is rejected. wiki: device-input-flow + dingtian-relay updated.
This commit is contained in:
2026-06-14 18:34:35 +02:00
parent 0375227a16
commit 7fd407ac82
6 changed files with 259 additions and 32 deletions
+22
View File
@@ -126,6 +126,28 @@ export function hasPushConfig(
return typeof (device as Partial<PushConfigurableDevice>).configureInputPush === "function";
}
// --- Hardening (lock the device down) ------------------------------------
// Optional capability: a device that can be hardened against a flat (no-VLAN)
// network — disable unused protocols/channels, set a relay password, and change
// the default web/config login. Returns any secrets the backend must persist to
// keep talking to the device. See wiki/concepts/device-input-flow.md.
export interface HardenableDevice {
harden(): Promise<HardenResult>;
}
export interface HardenResult {
/** Secrets to persist in lane_devices so the backend can keep operating the
* device (relay password, new web login). The backend merges these into the
* stored config. */
readonly secrets: Record<string, string | number>;
/** Human-readable summary of what was changed (for logging/UI). */
readonly applied: string[];
}
export function isHardenable(device: Device): device is Device & HardenableDevice {
return typeof (device as Partial<HardenableDevice>).harden === "function";
}
// --- Readers (RF / optical; TCP-IP or Wiegand) ---------------------------
export interface ReaderDevice extends Device {
/** Emits when a credential is read (card number, plate, QR payload, …). */