@@ -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 ( ) ;
0 commit comments