From 461becc3d34afce0a8f03dec6d4b58ae55dc5c53 Mon Sep 17 00:00:00 2001 From: saltbo Date: Sun, 2 Aug 2026 17:15:17 -0400 Subject: [PATCH] fix(oauth): synchronize Realmroot client scopes --- scripts/backfill-oauth-scopes.ts | 8 +++++++- server/scripts/backfill-oauth-scopes.test.ts | 18 +++++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/scripts/backfill-oauth-scopes.ts b/scripts/backfill-oauth-scopes.ts index c4dc89eb..be23f56f 100644 --- a/scripts/backfill-oauth-scopes.ts +++ b/scripts/backfill-oauth-scopes.ts @@ -22,6 +22,7 @@ interface OAuthResourceRow { interface OAuthClientRow { id: string + name?: string | null scopes: string | null } @@ -43,6 +44,11 @@ export function buildOAuthScopeBackfill( ), clients: clients.flatMap((client) => { const scopes = parseScopes(client.scopes) + if (client.name === 'Realmroot ZPan') { + return scopes.length === OAUTH_SCOPES.length && OAUTH_SCOPES.every((scope) => scopes.includes(scope)) + ? [] + : [{ id: client.id, scopes: JSON.stringify(OAUTH_SCOPES) }] + } if (!scopes.includes(AuthorizationScope.OBJECTS_CREATE) || scopes.includes(AuthorizationScope.QUOTA_PURCHASE)) { return [] } @@ -124,7 +130,7 @@ function readRows( execute: OAuthScopeD1Executor, ): { resources: OAuthResourceRow[]; clients: OAuthClientRow[] } { const resourceSql = 'SELECT id, name, allowed_scopes AS allowedScopes FROM oauthResource;' - const clientSql = 'SELECT id, scopes FROM oauthClient;' + const clientSql = 'SELECT id, name, scopes FROM oauthClient;' if (target.kind === 'd1') { return { resources: d1Rows(target, resourceSql, execute), diff --git a/server/scripts/backfill-oauth-scopes.test.ts b/server/scripts/backfill-oauth-scopes.test.ts index c72099ec..4ec230dd 100644 --- a/server/scripts/backfill-oauth-scopes.test.ts +++ b/server/scripts/backfill-oauth-scopes.test.ts @@ -24,7 +24,7 @@ function createScopeDatabase() { const db = new Database(path) db.exec(` CREATE TABLE oauthResource (id TEXT PRIMARY KEY, name TEXT NOT NULL, allowed_scopes TEXT, updated_at INTEGER); - CREATE TABLE oauthClient (id TEXT PRIMARY KEY, scopes TEXT, updated_at INTEGER); + CREATE TABLE oauthClient (id TEXT PRIMARY KEY, name TEXT, scopes TEXT, updated_at INTEGER); `) return { db, path } } @@ -45,6 +45,11 @@ describe('buildOAuthScopeBackfill', () => { }, ], [ + { + id: 'realmroot-client', + name: 'Realmroot ZPan', + scopes: JSON.stringify([AuthorizationScope.OBJECTS_CREATE, AuthorizationScope.QUOTA_PURCHASE]), + }, { id: 'upload-client', scopes: JSON.stringify([AuthorizationScope.OBJECTS_CREATE]), @@ -59,6 +64,10 @@ describe('buildOAuthScopeBackfill', () => { expect(changes).toEqual({ resources: [{ id: 'zpan-resource', scopes: JSON.stringify(OAUTH_SCOPES) }], clients: [ + { + id: 'realmroot-client', + scopes: JSON.stringify(OAUTH_SCOPES), + }, { id: 'upload-client', scopes: JSON.stringify([AuthorizationScope.OBJECTS_CREATE, AuthorizationScope.QUOTA_PURCHASE]), @@ -141,7 +150,9 @@ describe('buildOAuthScopeBackfill', () => { .mockReturnValueOnce(JSON.stringify([{ results: [{ id: "resource'1", name: 'ZPan API', allowedScopes: '[]' }] }])) .mockReturnValueOnce( JSON.stringify([ - { results: [{ id: 'client-1', scopes: JSON.stringify([AuthorizationScope.OBJECTS_CREATE]) }] }, + { + results: [{ id: 'client-1', name: null, scopes: JSON.stringify([AuthorizationScope.OBJECTS_CREATE]) }], + }, ]), ) .mockReturnValueOnce('') @@ -149,7 +160,7 @@ describe('buildOAuthScopeBackfill', () => { .mockReturnValueOnce( JSON.stringify([{ results: [{ id: "resource'1", name: 'ZPan API', allowedScopes: resourceScopes }] }]), ) - .mockReturnValueOnce(JSON.stringify([{ results: [{ id: 'client-1', scopes: clientScopes }] }])) + .mockReturnValueOnce(JSON.stringify([{ results: [{ id: 'client-1', name: null, scopes: clientScopes }] }])) runOAuthScopeBackfill(['--d1', 'zpan-db', '--remote', '--env', 'production', '--apply'], () => {}, execute) @@ -161,6 +172,7 @@ describe('buildOAuthScopeBackfill', () => { env: 'production', }) expect(execute.mock.calls[0]?.[2]).toBe(true) + expect(execute.mock.calls[1]?.[1]).toBe('SELECT id, name, scopes FROM oauthClient;') expect(execute.mock.calls[2]?.[1]).toContain("resource''1") }) })