diff --git a/packages/store/src/cli/services/store/auth/config.ts b/packages/store/src/cli/services/store/auth/config.ts index 94ce1f1ea5..46b72e6a7d 100644 --- a/packages/store/src/cli/services/store/auth/config.ts +++ b/packages/store/src/cli/services/store/auth/config.ts @@ -7,7 +7,11 @@ export function storeAuthRedirectUri(port: number): string { } export function storeAuthSessionKey(store: string): string { - return `${STORE_AUTH_APP_CLIENT_ID}::${store}` + return `${STORE_AUTH_APP_CLIENT_ID}::${escapeStoreAuthSessionKeySegment(store)}` +} + +function escapeStoreAuthSessionKeySegment(value: string): string { + return value.replace(/\./g, '\\.') } export function maskToken(token: string): string { diff --git a/packages/store/src/cli/services/store/auth/session-store.ts b/packages/store/src/cli/services/store/auth/session-store.ts index a039c08edf..0acdb46232 100644 --- a/packages/store/src/cli/services/store/auth/session-store.ts +++ b/packages/store/src/cli/services/store/auth/session-store.ts @@ -212,26 +212,6 @@ function readRawStoreSessionStorage(storage: LocalStorage): return (storage as unknown as {config?: {store?: RawStoreSessionStorage}}).config?.store ?? {} } -function collectCurrentStoredStoreAppSessions( - storage: LocalStorage, - store: string, - value: unknown, - sessions: StoredStoreAppSession[], -): void { - if (!value || typeof value !== 'object' || Array.isArray(value)) return - - const bucket = sanitizeStoredStoreAppSessionBucket(store, value, storage) - if (bucket) { - const session = bucket.sessionsByUserId[bucket.currentUserId] - if (session) sessions.push(session) - return - } - - for (const [childKey, childValue] of Object.entries(value as RawStoreSessionStorage)) { - collectCurrentStoredStoreAppSessions(storage, `${store}.${childKey}`, childValue, sessions) - } -} - /** * Internal persistence helper for projecting the current session for every * store that has locally stored store auth. @@ -244,7 +224,10 @@ export function listCurrentStoredStoreAppSessions( for (const [key, value] of Object.entries(readRawStoreSessionStorage(storage))) { if (!key.startsWith(keyPrefix)) continue - collectCurrentStoredStoreAppSessions(storage, key.slice(keyPrefix.length), value, sessions) + + const bucket = sanitizeStoredStoreAppSessionBucket(key.slice(keyPrefix.length), value, storage) + const session = bucket?.sessionsByUserId[bucket.currentUserId] + if (session) sessions.push(session) } return sessions diff --git a/packages/store/src/cli/services/store/auth/stored-auth.test.ts b/packages/store/src/cli/services/store/auth/stored-auth.test.ts index f1b8ba6967..c2d1a0f960 100644 --- a/packages/store/src/cli/services/store/auth/stored-auth.test.ts +++ b/packages/store/src/cli/services/store/auth/stored-auth.test.ts @@ -66,6 +66,39 @@ describe('listStoredStoreAuthSummaries', () => { }) }) + test('persists dotted store domains as a single top-level key', async () => { + await inTemporaryDirectory((cwd) => { + const storage = new LocalStorage>({cwd}) + + setStoredStoreAppSession(buildSession(), storage as any) + + const rawStorage = (storage as unknown as {config: {store: Record}}).config.store + expect(Object.keys(rawStorage)).toContain(`${STORE_AUTH_APP_CLIENT_ID}::shop.myshopify.com`) + expect(listStoredStoreAuthSummaries(storage as any)).toEqual([ + { + store: 'shop.myshopify.com', + userId: '42', + scopes: ['read_products'], + acquiredAt: '2026-03-27T00:00:00.000Z', + }, + ]) + }) + }) + + test('ignores legacy nested buckets written with unescaped dotted domains', async () => { + await inTemporaryDirectory((cwd) => { + const storage = new LocalStorage>({cwd}) + storage.set(`${STORE_AUTH_APP_CLIENT_ID}::legacy.myshopify.com`, { + currentUserId: '42', + sessionsByUserId: { + '42': buildSession({store: 'legacy.myshopify.com'}), + }, + }) + + expect(listStoredStoreAuthSummaries(storage as any)).toEqual([]) + }) + }) + test('sorts stores alphabetically when auth timestamps match', async () => { await inTemporaryDirectory((cwd) => { const storage = new LocalStorage>({cwd})