From b9aed3a07c45a6e2469e07b385495dbcba438a2a Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 17 Feb 2026 00:10:41 +0000 Subject: [PATCH] refactor(infra): reuse device auth scope normalization --- src/infra/device-pairing.ts | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/src/infra/device-pairing.ts b/src/infra/device-pairing.ts index a6a9c7fcdf..8a0dab286e 100644 --- a/src/infra/device-pairing.ts +++ b/src/infra/device-pairing.ts @@ -1,4 +1,5 @@ import { randomUUID } from "node:crypto"; +import { normalizeDeviceAuthScopes } from "../shared/device-auth.js"; import { createAsyncLock, pruneExpiredPending, @@ -150,20 +151,6 @@ function mergeScopes(...items: Array): string[] | undefine return [...scopes]; } -function normalizeScopes(scopes: string[] | undefined): string[] { - if (!Array.isArray(scopes)) { - return []; - } - const out = new Set(); - for (const scope of scopes) { - const trimmed = scope.trim(); - if (trimmed) { - out.add(trimmed); - } - } - return [...out].toSorted(); -} - function scopesAllow(requested: string[], allowed: string[]): boolean { if (requested.length === 0) { return true; @@ -283,7 +270,7 @@ export async function approveDevicePairing( const tokens = existing?.tokens ? { ...existing.tokens } : {}; const roleForToken = normalizeRole(pending.role); if (roleForToken) { - const nextScopes = normalizeScopes(pending.scopes); + const nextScopes = normalizeDeviceAuthScopes(pending.scopes); const existingToken = tokens[roleForToken]; const now = Date.now(); tokens[roleForToken] = { @@ -407,7 +394,7 @@ export async function verifyDeviceToken(params: { if (!verifyPairingToken(params.token, entry.token)) { return { ok: false, reason: "token-mismatch" }; } - const requestedScopes = normalizeScopes(params.scopes); + const requestedScopes = normalizeDeviceAuthScopes(params.scopes); if (!scopesAllow(requestedScopes, entry.scopes)) { return { ok: false, reason: "scope-mismatch" }; } @@ -428,7 +415,7 @@ export async function ensureDeviceToken(params: { }): Promise { return await withLock(async () => { const state = await loadState(params.baseDir); - const requestedScopes = normalizeScopes(params.scopes); + const requestedScopes = normalizeDeviceAuthScopes(params.scopes); const context = resolveDeviceTokenUpdateContext({ state, deviceId: params.deviceId, @@ -499,7 +486,9 @@ export async function rotateDeviceToken(params: { return null; } const { device, role, tokens, existing } = context; - const requestedScopes = normalizeScopes(params.scopes ?? existing?.scopes ?? device.scopes); + const requestedScopes = normalizeDeviceAuthScopes( + params.scopes ?? existing?.scopes ?? device.scopes, + ); const now = Date.now(); const next = buildDeviceAuthToken({ role,