Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions packages/store/src/cli/services/store/auth/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,10 +304,47 @@ describe('store auth service', () => {
expect(presenter.openingBrowser).toHaveBeenCalledOnce()
expect(presenter.manualAuthUrl).toHaveBeenCalledWith(
expect.stringContaining('https://shop.myshopify.com/admin/oauth/authorize?'),
{sensitive: false},
)
expect(presenter.success).toHaveBeenCalledWith(result)
})

test('authenticateStoreWithApp marks manual auth URL as sensitive when signup JWT is present', async () => {
const openURL = vi.fn().mockResolvedValue(false)
const presenter = {
openingBrowser: vi.fn(),
manualAuthUrl: vi.fn(),
success: vi.fn(),
}
const waitForStoreAuthCodeMock = vi.fn().mockImplementation(async (options) => {
await options.onListening?.()
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,
},
)

expect(presenter.manualAuthUrl).toHaveBeenCalledWith(expect.stringContaining('signup=signed.signup.jwt'), {
sensitive: true,
})
})

test('authenticateStoreWithApp records fqdn metadata before resolving existing scopes', async () => {
await expect(
authenticateStoreWithApp(
Expand Down
2 changes: 1 addition & 1 deletion packages/store/src/cli/services/store/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export async function authenticateStoreWithApp(
...bootstrap.waitForAuthCodeOptions,
onListening: async () => {
const opened = await resolvedDependencies.openURL(authorizationUrl)
if (!opened) resolvedDependencies.presenter.manualAuthUrl(authorizationUrl)
if (!opened) resolvedDependencies.presenter.manualAuthUrl(authorizationUrl, {sensitive: Boolean(input.signup)})
},
})
const tokenResponse = await bootstrap.exchangeCodeForToken(code)
Expand Down
18 changes: 18 additions & 0 deletions packages/store/src/cli/services/store/auth/result.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,22 @@ describe('store auth presenter', () => {
expect(streams.stdout()).toContain('"store": "shop.myshopify.com"')
expect(streams.stdout()).not.toContain('Authenticated')
})

test('does not print manual auth URL output when marked sensitive', () => {
const output = mockAndCaptureOutput()
const presenter = createStoreAuthPresenter('text')

presenter.manualAuthUrl('https://shop.myshopify.com/admin/oauth/authorize?client_id=test&secret=sensitive', {
sensitive: true,
})

expect(output.info()).toContain(
'Browser did not open automatically. The manual authorization URL contains sensitive credentials and was not printed.',
)
expect(output.info()).toContain(
'Run this command again in an environment where Shopify CLI can open a browser automatically.',
)
expect(output.info()).not.toContain('secret=sensitive')
expect(output.info()).not.toContain('https://shop.myshopify.com/admin/oauth/authorize')
})
})
17 changes: 15 additions & 2 deletions packages/store/src/cli/services/store/auth/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ export interface StoreAuthResult {

type StoreAuthOutputFormat = 'text' | 'json'

interface ManualAuthUrlOptions {
sensitive?: boolean
}

export interface StoreAuthPresenter {
openingBrowser: () => void
manualAuthUrl: (authorizationUrl: string) => void
manualAuthUrl: (authorizationUrl: string, options?: ManualAuthUrlOptions) => void
success: (result: StoreAuthResult) => void
}

Expand All @@ -47,7 +51,16 @@ function displayStoreAuthOpeningBrowser(): void {
outputInfo('')
}

function displayStoreAuthManualAuthUrl(authorizationUrl: string): void {
function displayStoreAuthManualAuthUrl(authorizationUrl: string, options: ManualAuthUrlOptions = {}): void {
if (options.sensitive) {
outputInfo(
'Browser did not open automatically. The manual authorization URL contains sensitive credentials and was not printed.',
)
outputInfo('Run this command again in an environment where Shopify CLI can open a browser automatically.')
outputInfo('')
return
}

outputInfo('Browser did not open automatically. Open this URL manually:')
outputInfo(outputContent`${outputToken.link(authorizationUrl)}`)
outputInfo('')
Expand Down
Loading