Files
parking_solution/apps/server/src/routes/setup-secrets.test.ts
T
julian 420542ce10 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
2026-06-24 19:03:53 +02:00

59 lines
2.2 KiB
TypeScript

import { beforeEach, describe, expect, it } from "vitest";
import { randomUUID } from "node:crypto";
import { devices, type Db } from "@parking/db";
import { createTestDb } from "@parking/db/testing";
import { storedSecrets } from "./setup.js";
// storedSecrets re-merges a device's machine-only secrets (relayPassword/pushPassword)
// into a test/save — but ONLY when the submitted config addresses the SAME device at the
// SAME host/port. This guards against a redirected probe exfiltrating the secret to an
// attacker host (an admin keeps a real device id but swaps the host). The booth operator
// is the threat-model adversary, so an authenticated-admin redirect must NOT leak.
let db: Db;
const ID = "ctl-secret";
const HOST = "10.0.10.5";
beforeEach(() => {
({ db } = createTestDb());
db.insert(devices).values({
id: ID,
category: "access",
driverId: "dingtian",
config: { host: HOST, binaryPort: 60000, relayPassword: 1996, pushPassword: "p-secret" },
enabled: true,
}).run();
});
describe("storedSecrets identity guard", () => {
it("re-merges secrets when host/port/driver match the stored device", () => {
const out = storedSecrets(db, ID, "dingtian", { host: HOST, binaryPort: 60000 });
expect(out.relayPassword).toBe(1996);
expect(out.pushPassword).toBe("p-secret");
});
it("re-merges when identity fields are OMITTED (fall back to the stored device)", () => {
const out = storedSecrets(db, ID, "dingtian", {});
expect(out.relayPassword).toBe(1996);
});
it("REFUSES secrets when the host is redirected (exfiltration attempt)", () => {
const out = storedSecrets(db, ID, "dingtian", { host: "10.66.66.66", binaryPort: 60000 });
expect(out).toEqual({});
});
it("REFUSES secrets when a control port is changed", () => {
const out = storedSecrets(db, ID, "dingtian", { host: HOST, binaryPort: 9999 });
expect(out).toEqual({});
});
it("REFUSES secrets when the driver doesn't match the stored row", () => {
const out = storedSecrets(db, ID, "stub-access", { host: HOST });
expect(out).toEqual({});
});
it("returns nothing for an unknown device id", () => {
expect(storedSecrets(db, randomUUID(), "dingtian", { host: HOST })).toEqual({});
});
});