mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
Gateway: tolerate legacy paired metadata for scope upgrades
This commit is contained in:
@@ -948,6 +948,66 @@ describe("gateway server auth/connect", () => {
|
||||
restoreGatewayToken(prevToken);
|
||||
});
|
||||
|
||||
test("allows legacy paired devices without role/scope metadata", async () => {
|
||||
const { resolvePairingPaths, readJsonFile } = await import("../infra/pairing-files.js");
|
||||
const { writeJsonAtomic } = await import("../infra/json-files.js");
|
||||
const { getPairedDevice } = await import("../infra/device-pairing.js");
|
||||
const {
|
||||
device,
|
||||
identity: { deviceId },
|
||||
} = await createSignedDevice({
|
||||
token: "secret",
|
||||
scopes: ["operator.read"],
|
||||
clientId: TEST_OPERATOR_CLIENT.id,
|
||||
clientMode: TEST_OPERATOR_CLIENT.mode,
|
||||
});
|
||||
const { server, ws, port, prevToken } = await startServerWithClient("secret");
|
||||
let ws2: WebSocket | undefined;
|
||||
try {
|
||||
const initial = await connectReq(ws, {
|
||||
token: "secret",
|
||||
scopes: ["operator.read"],
|
||||
client: TEST_OPERATOR_CLIENT,
|
||||
device,
|
||||
});
|
||||
if (!initial.ok) {
|
||||
await approvePendingPairingIfNeeded();
|
||||
}
|
||||
|
||||
const { pairedPath } = resolvePairingPaths(undefined, "devices");
|
||||
const paired =
|
||||
(await readJsonFile<Record<string, { roles?: string[]; scopes?: string[] }>>(pairedPath)) ??
|
||||
{};
|
||||
const legacy = paired[deviceId];
|
||||
expect(legacy).toBeDefined();
|
||||
expect(legacy?.roles).toBeUndefined();
|
||||
expect(legacy?.scopes).toBeUndefined();
|
||||
delete legacy.roles;
|
||||
delete legacy.scopes;
|
||||
await writeJsonAtomic(pairedPath, paired);
|
||||
ws.close();
|
||||
|
||||
ws2 = new WebSocket(`ws://127.0.0.1:${port}`);
|
||||
await new Promise<void>((resolve) => ws2.once("open", resolve));
|
||||
const upgraded = await connectReq(ws2, {
|
||||
token: "secret",
|
||||
scopes: ["operator.admin"],
|
||||
client: TEST_OPERATOR_CLIENT,
|
||||
device,
|
||||
});
|
||||
expect(upgraded.ok).toBe(true);
|
||||
|
||||
const refreshed = await getPairedDevice(deviceId);
|
||||
expect(refreshed?.roles).toContain("operator");
|
||||
expect(refreshed?.scopes).toContain("operator.admin");
|
||||
} finally {
|
||||
await server.close();
|
||||
restoreGatewayToken(prevToken);
|
||||
ws.close();
|
||||
ws2?.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("rejects revoked device token", async () => {
|
||||
const { revokeDeviceToken } = await import("../infra/device-pairing.js");
|
||||
const { server, ws, port, prevToken } = await startServerWithClient("secret");
|
||||
|
||||
@@ -711,43 +711,47 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const hasLegacyPairedMetadata =
|
||||
paired.roles === undefined && paired.scopes === undefined;
|
||||
const pairedRoles = Array.isArray(paired.roles)
|
||||
? paired.roles
|
||||
: paired.role
|
||||
? [paired.role]
|
||||
: [];
|
||||
const allowedRoles = new Set(pairedRoles);
|
||||
if (allowedRoles.size === 0) {
|
||||
logUpgradeAudit("role-upgrade", pairedRoles, paired.scopes);
|
||||
const ok = await requirePairing("role-upgrade");
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
} else if (!allowedRoles.has(role)) {
|
||||
logUpgradeAudit("role-upgrade", pairedRoles, paired.scopes);
|
||||
const ok = await requirePairing("role-upgrade");
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const pairedScopes = Array.isArray(paired.scopes) ? paired.scopes : [];
|
||||
if (scopes.length > 0) {
|
||||
if (pairedScopes.length === 0) {
|
||||
logUpgradeAudit("scope-upgrade", pairedRoles, pairedScopes);
|
||||
const ok = await requirePairing("scope-upgrade");
|
||||
if (!hasLegacyPairedMetadata) {
|
||||
const allowedRoles = new Set(pairedRoles);
|
||||
if (allowedRoles.size === 0) {
|
||||
logUpgradeAudit("role-upgrade", pairedRoles, paired.scopes);
|
||||
const ok = await requirePairing("role-upgrade");
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const allowedScopes = new Set(pairedScopes);
|
||||
const missingScope = scopes.find((scope) => !allowedScopes.has(scope));
|
||||
if (missingScope) {
|
||||
} else if (!allowedRoles.has(role)) {
|
||||
logUpgradeAudit("role-upgrade", pairedRoles, paired.scopes);
|
||||
const ok = await requirePairing("role-upgrade");
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const pairedScopes = Array.isArray(paired.scopes) ? paired.scopes : [];
|
||||
if (scopes.length > 0) {
|
||||
if (pairedScopes.length === 0) {
|
||||
logUpgradeAudit("scope-upgrade", pairedRoles, pairedScopes);
|
||||
const ok = await requirePairing("scope-upgrade");
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const allowedScopes = new Set(pairedScopes);
|
||||
const missingScope = scopes.find((scope) => !allowedScopes.has(scope));
|
||||
if (missingScope) {
|
||||
logUpgradeAudit("scope-upgrade", pairedRoles, pairedScopes);
|
||||
const ok = await requirePairing("scope-upgrade");
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user