diff --git a/src/infra/device-bootstrap.test.ts b/src/infra/device-bootstrap.test.ts index 8337a68cf87..8fd356edf06 100644 --- a/src/infra/device-bootstrap.test.ts +++ b/src/infra/device-bootstrap.test.ts @@ -238,6 +238,26 @@ describe("device bootstrap tokens", () => { ).resolves.toEqual({ ok: false, reason: "bootstrap_token_invalid" }); }); + it("accepts constrained tokens when the requested role and scopes match", async () => { + const baseDir = await createTempDir(); + const issued = await issueDeviceBootstrapToken({ + baseDir, + role: "node", + scopes: [], + }); + + await expect( + verifyDeviceBootstrapToken({ + token: issued.token, + deviceId: "device-123", + publicKey: "public-key-123", + role: "node", + scopes: [], + baseDir, + }), + ).resolves.toEqual({ ok: true }); + }); + it("rejects scopes that do not match the issued pairing profile", async () => { const baseDir = await createTempDir(); const issued = await issueDeviceBootstrapToken({ diff --git a/src/infra/device-bootstrap.ts b/src/infra/device-bootstrap.ts index ff8509555e7..7eb63429f6d 100644 --- a/src/infra/device-bootstrap.ts +++ b/src/infra/device-bootstrap.ts @@ -124,6 +124,8 @@ export async function verifyDeviceBootstrapToken(params: { } if (Array.isArray(entry.scopes)) { const allowedScopes = normalizeDeviceAuthScopes(entry.scopes); + // Both arrays are normalized through normalizeDeviceAuthScopes, which + // sorts and deduplicates them before comparison. if ( allowedScopes.length !== requestedScopes.length || allowedScopes.some((value, index) => value !== requestedScopes[index])