Gateway: preserve interactive pairing visibility on supersede
This commit is contained in:
parent
a953cb5209
commit
6309b1da6c
@ -162,6 +162,36 @@ describe("device pairing tokens", () => {
|
|||||||
expect(paired?.scopes).toEqual(["operator.read", "operator.write"]);
|
expect(paired?.scopes).toEqual(["operator.read", "operator.write"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("keeps superseded requests interactive when an existing pending request is interactive", async () => {
|
||||||
|
const baseDir = await mkdtemp(join(tmpdir(), "openclaw-device-pairing-"));
|
||||||
|
const first = await requestDevicePairing(
|
||||||
|
{
|
||||||
|
deviceId: "device-1",
|
||||||
|
publicKey: "public-key-1",
|
||||||
|
role: "node",
|
||||||
|
scopes: [],
|
||||||
|
silent: false,
|
||||||
|
},
|
||||||
|
baseDir,
|
||||||
|
);
|
||||||
|
expect(first.request.silent).toBe(false);
|
||||||
|
|
||||||
|
const second = await requestDevicePairing(
|
||||||
|
{
|
||||||
|
deviceId: "device-1",
|
||||||
|
publicKey: "public-key-1",
|
||||||
|
role: "operator",
|
||||||
|
scopes: ["operator.read"],
|
||||||
|
silent: true,
|
||||||
|
},
|
||||||
|
baseDir,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(second.created).toBe(true);
|
||||||
|
expect(second.request.requestId).not.toBe(first.request.requestId);
|
||||||
|
expect(second.request.silent).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
test("rejects bootstrap token replay before pending scope escalation can be approved", async () => {
|
test("rejects bootstrap token replay before pending scope escalation can be approved", async () => {
|
||||||
const baseDir = await mkdtemp(join(tmpdir(), "openclaw-device-pairing-"));
|
const baseDir = await mkdtemp(join(tmpdir(), "openclaw-device-pairing-"));
|
||||||
const issued = await issueDeviceBootstrapToken({ baseDir });
|
const issued = await issueDeviceBootstrapToken({ baseDir });
|
||||||
|
|||||||
@ -236,6 +236,15 @@ function refreshPendingDevicePairingRequest(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveSupersededPendingSilent(params: {
|
||||||
|
existing: readonly DevicePairingPendingRequest[];
|
||||||
|
incomingSilent: boolean | undefined;
|
||||||
|
}): boolean {
|
||||||
|
return Boolean(
|
||||||
|
params.incomingSilent && params.existing.every((pending) => pending.silent === true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function buildPendingDevicePairingRequest(params: {
|
function buildPendingDevicePairingRequest(params: {
|
||||||
requestId?: string;
|
requestId?: string;
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
@ -394,7 +403,15 @@ export async function requestDevicePairing(
|
|||||||
const superseded = buildPendingDevicePairingRequest({
|
const superseded = buildPendingDevicePairingRequest({
|
||||||
deviceId,
|
deviceId,
|
||||||
isRepair,
|
isRepair,
|
||||||
req,
|
req: {
|
||||||
|
...req,
|
||||||
|
// Preserve interactive visibility when superseding pending requests:
|
||||||
|
// if any previous pending request was interactive, keep this one interactive.
|
||||||
|
silent: resolveSupersededPendingSilent({
|
||||||
|
existing: pendingForDevice,
|
||||||
|
incomingSilent: req.silent,
|
||||||
|
}),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
state.pendingById[superseded.requestId] = superseded;
|
state.pendingById[superseded.requestId] = superseded;
|
||||||
await persistState(state, baseDir);
|
await persistState(state, baseDir);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user