fix(setup): add Dingtian relay-password field + secure secret re-merge on test

The relay control password (relay_pw) was read by the driver but had NO form field,
so Test connection sent it as 0 → the device ignored the probe → a controller showed
"offline" even though it pinged. Add a "Relay control password" config field (secret;
blank keeps the stored value).

Because relayPassword is redacted from the client, the edit form can't resend it — so
the test endpoint now re-merges the stored secret by device id (mirroring save). It is
re-merged ONLY when the submitted config addresses the SAME device: matching driverId
and every connection-identity field it sets (host/port/binaryPort/httpPort/serial). A
redirected host/port or mismatched driver yields NO secret, so a probe can't exfiltrate
the password to an attacker host (the booth operator is the threat-model adversary).
testDevice() now passes the device id; setup-secrets.test.ts covers the identity guard.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
2026-06-24 19:03:53 +02:00
parent 2915d141aa
commit 420542ce10
4 changed files with 135 additions and 5 deletions
+5 -3
View File
@@ -312,11 +312,13 @@ export interface TestResult {
};
}
/** Test a device config (reachability + preconditions) without saving. */
export function testDevice(driverId: string, config: DeviceConfig): Promise<TestResult> {
/** Test a device config (reachability + preconditions) without saving. Pass the
* device `id` when editing an existing one so the server re-merges its stored
* machine secrets (e.g. the relay password redacted from the client). */
export function testDevice(driverId: string, config: DeviceConfig, id?: string): Promise<TestResult> {
return apiFetch<TestResult>("/api/setup/test", {
method: "POST",
body: JSON.stringify({ driverId, config }),
body: JSON.stringify({ driverId, config, ...(id ? { id } : {}) }),
});
}