diff --git a/packages/store/src/cli/services/store/auth/index.test.ts b/packages/store/src/cli/services/store/auth/index.test.ts index 4969769e491..df839923cf8 100644 --- a/packages/store/src/cli/services/store/auth/index.test.ts +++ b/packages/store/src/cli/services/store/auth/index.test.ts @@ -321,30 +321,65 @@ describe('store auth service', () => { return 'abc123' }) - await authenticateStoreWithApp( - { - store: 'shop.myshopify.com', - scopes: 'read_products', - signup: 'signed.signup.jwt', - }, - { - openURL, - waitForStoreAuthCode: waitForStoreAuthCodeMock, - exchangeStoreAuthCodeForToken: vi.fn().mockResolvedValue({ - access_token: 'token', - scope: 'read_products', - expires_in: 86400, - associated_user: {id: 42, email: 'test@example.com'}, - }), - presenter, - }, - ) + await expect( + authenticateStoreWithApp( + { + store: 'shop.myshopify.com', + scopes: 'read_products', + signup: 'signed.signup.jwt', + }, + { + openURL, + waitForStoreAuthCode: waitForStoreAuthCodeMock, + exchangeStoreAuthCodeForToken: vi.fn().mockResolvedValue({ + access_token: 'token', + scope: 'read_products', + expires_in: 86400, + associated_user: {id: 42, email: 'test@example.com'}, + }), + presenter, + }, + ), + ).rejects.toThrow() expect(presenter.manualAuthUrl).toHaveBeenCalledWith(expect.stringContaining('signup=signed.signup.jwt'), { sensitive: true, }) }) + test('authenticateStoreWithApp fails immediately instead of waiting for a callback that cannot arrive', async () => { + const openURL = vi.fn().mockResolvedValue(false) + const presenter = { + openingBrowser: vi.fn(), + manualAuthUrl: vi.fn(), + success: vi.fn(), + } + const exchangeStoreAuthCodeForToken = vi.fn() + const waitForStoreAuthCodeMock = vi.fn().mockImplementation(async (options) => { + await options.onListening?.() + return 'abc123' + }) + + await expect( + authenticateStoreWithApp( + { + store: 'shop.myshopify.com', + scopes: 'read_products', + signup: 'signed.signup.jwt', + }, + { + openURL, + waitForStoreAuthCode: waitForStoreAuthCodeMock, + exchangeStoreAuthCodeForToken, + presenter, + }, + ), + ).rejects.toThrow("Authentication can't continue without a browser.") + + expect(exchangeStoreAuthCodeForToken).not.toHaveBeenCalled() + expect(presenter.success).not.toHaveBeenCalled() + }) + test('authenticateStoreWithApp records fqdn metadata before resolving existing scopes', async () => { await expect( authenticateStoreWithApp( diff --git a/packages/store/src/cli/services/store/auth/index.ts b/packages/store/src/cli/services/store/auth/index.ts index f342ae5074b..dcf179e40ac 100644 --- a/packages/store/src/cli/services/store/auth/index.ts +++ b/packages/store/src/cli/services/store/auth/index.ts @@ -76,7 +76,14 @@ export async function authenticateStoreWithApp( ...bootstrap.waitForAuthCodeOptions, onListening: async () => { const opened = await resolvedDependencies.openURL(authorizationUrl) - if (!opened) resolvedDependencies.presenter.manualAuthUrl(authorizationUrl, {sensitive: Boolean(input.signup)}) + if (opened) return + + const sensitive = Boolean(input.signup) + resolvedDependencies.presenter.manualAuthUrl(authorizationUrl, {sensitive}) + + // A withheld URL never reaches the browser, so the callback this server is waiting for cannot + // arrive. Returning here would leave the command idle until the timeout elapses. + if (sensitive) throw new AbortError("Authentication can't continue without a browser.") }, }) const tokenResponse = await bootstrap.exchangeCodeForToken(code)