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.
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import { networkInterfaces } from "node:os";
|
||||
|
||||
// Figure out which local IP a device should call back on. For input-push, the
|
||||
// device needs OUR address on ITS subnet — pick the local IPv4 interface whose
|
||||
// network contains the device's IP. Override with BACKEND_HOST_IP if the
|
||||
// auto-pick is wrong (e.g. multi-homed host). See wiki/concepts/device-input-flow.md.
|
||||
|
||||
export function backendIpForDevice(deviceHost: string): string | null {
|
||||
if (process.env.BACKEND_HOST_IP) return process.env.BACKEND_HOST_IP;
|
||||
|
||||
const ip = deviceHost.split(".").map(Number);
|
||||
if (ip.length !== 4 || ip.some((o) => Number.isNaN(o))) return null;
|
||||
|
||||
for (const ifaces of Object.values(networkInterfaces())) {
|
||||
for (const i of ifaces ?? []) {
|
||||
if (i.family !== "IPv4" || i.internal) continue;
|
||||
const addr = i.address.split(".").map(Number);
|
||||
const mask = i.netmask.split(".").map(Number);
|
||||
if (addr.length !== 4 || mask.length !== 4) continue;
|
||||
const sameNet = ip.every((o, k) => (o & mask[k]!) === (addr[k]! & mask[k]!));
|
||||
if (sameNet) return i.address;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Backend port the device should call (the server's listen port). */
|
||||
export function backendPort(): number {
|
||||
return Number(process.env.PORT ?? 3000);
|
||||
}
|
||||
Reference in New Issue
Block a user