Skip to content

Commit be7a415

Browse files
authored
fix: preserve hello-ok scopes for reused device tokens (openclaw#68039)
1 parent f377db1 commit be7a415

File tree

2 files changed

+65
-1
lines changed

2 files changed

+65
-1
lines changed

src/gateway/server.auth.default-token.suite.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,69 @@ export function registerDefaultAuthTokenSuite(): void {
270270
}
271271
});
272272

273+
test("hello-ok reports persisted token scopes when reusing an existing device token", async () => {
274+
const { randomUUID } = await import("node:crypto");
275+
const os = await import("node:os");
276+
const path = await import("node:path");
277+
const token = resolveGatewayTokenOrEnv();
278+
const deviceIdentityPath = path.join(
279+
os.tmpdir(),
280+
`openclaw-shared-auth-scope-reuse-${randomUUID()}.json`,
281+
);
282+
const wsInitial = await openWs(port);
283+
let pairedDeviceToken: string | undefined;
284+
let pairedDeviceScopes: unknown;
285+
try {
286+
const initial = await connectReq(wsInitial, {
287+
token,
288+
scopes: ["operator.admin"],
289+
deviceIdentityPath,
290+
});
291+
expect(initial.ok).toBe(true);
292+
const helloOk = initial.payload as
293+
| {
294+
auth?: {
295+
role?: unknown;
296+
scopes?: unknown;
297+
deviceToken?: unknown;
298+
};
299+
}
300+
| undefined;
301+
expect(helloOk?.auth?.role).toBe("operator");
302+
expect(Array.isArray(helloOk?.auth?.scopes)).toBe(true);
303+
expect(typeof helloOk?.auth?.deviceToken).toBe("string");
304+
pairedDeviceToken = helloOk?.auth?.deviceToken as string | undefined;
305+
pairedDeviceScopes = helloOk?.auth?.scopes;
306+
} finally {
307+
wsInitial.close();
308+
}
309+
310+
const wsReconnect = await openWs(port);
311+
try {
312+
const reconnect = await connectReq(wsReconnect, {
313+
token,
314+
scopes: ["operator.read"],
315+
deviceIdentityPath,
316+
});
317+
expect(reconnect.ok).toBe(true);
318+
const helloOk = reconnect.payload as
319+
| {
320+
auth?: {
321+
role?: unknown;
322+
scopes?: unknown;
323+
deviceToken?: unknown;
324+
};
325+
}
326+
| undefined;
327+
expect(helloOk?.auth?.role).toBe("operator");
328+
expect(helloOk?.auth?.deviceToken).toBe(pairedDeviceToken);
329+
expect(helloOk?.auth?.scopes).toEqual(pairedDeviceScopes);
330+
expect(helloOk?.auth?.scopes).not.toEqual(["operator.read"]);
331+
} finally {
332+
wsReconnect.close();
333+
}
334+
});
335+
273336
test("does not grant admin when scopes are omitted", async () => {
274337
const ws = await openWs(port);
275338
const token = resolveGatewayTokenOrEnv();

src/gateway/server/ws-connection/message-handler.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1217,6 +1217,7 @@ export function attachGatewayWsMessageHandler(params: {
12171217
canvasHostUrl && canvasCapability
12181218
? (buildCanvasScopedHostUrl(canvasHostUrl, canvasCapability) ?? canvasHostUrl)
12191219
: canvasHostUrl;
1220+
const helloOkAuthScopes = deviceToken ? deviceToken.scopes : scopes;
12201221
const helloOk = {
12211222
type: "hello-ok",
12221223
protocol: PROTOCOL_VERSION,
@@ -1229,7 +1230,7 @@ export function attachGatewayWsMessageHandler(params: {
12291230
canvasHostUrl: scopedCanvasHostUrl,
12301231
auth: {
12311232
role,
1232-
scopes,
1233+
scopes: helloOkAuthScopes,
12331234
...(deviceToken
12341235
? {
12351236
deviceToken: deviceToken.token,

0 commit comments

Comments
 (0)