Skip to content

Commit 2e92296

Browse files
authored
Merge pull request #6003 from Shopify/fix-wh-legacy-install-error
Add validation to prevent app-specific webhooks and legacy install flow
2 parents 5969f8c + cf3a1f5 commit 2e92296

File tree

3 files changed

+162
-0
lines changed

3 files changed

+162
-0
lines changed

.changeset/purple-brooms-mate.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@shopify/app': patch
3+
---
4+
5+
Adds a validation to prevent app-specific webhooks and legacy install flow

packages/app/src/cli/models/app/app.test.ts

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {ApplicationURLs} from '../../services/dev/urls.js'
2929
import {describe, expect, test, vi} from 'vitest'
3030
import {inTemporaryDirectory, mkdir, readFile, writeFile} from '@shopify/cli-kit/node/fs'
3131
import {joinPath} from '@shopify/cli-kit/node/path'
32+
import {AbortError} from '@shopify/cli-kit/node/error'
3233

3334
const CORRECT_CURRENT_APP_SCHEMA: CurrentAppConfiguration = {
3435
path: '',
@@ -232,6 +233,134 @@ describe('getAppScopesArray', () => {
232233
})
233234
})
234235

236+
describe('preDeployValidation', () => {
237+
test('throws an error when app-specific webhooks are used with legacy install flow', async () => {
238+
// Given
239+
const configuration: CurrentAppConfiguration = {
240+
...DEFAULT_CONFIG,
241+
access_scopes: {
242+
scopes: 'read_orders',
243+
use_legacy_install_flow: true,
244+
},
245+
webhooks: {
246+
api_version: '2024-07',
247+
subscriptions: [
248+
{
249+
topics: ['orders/create'],
250+
uri: 'webhooks',
251+
},
252+
],
253+
},
254+
}
255+
const app = testApp({configuration})
256+
257+
// When/Then
258+
await expect(app.preDeployValidation()).rejects.toThrow(
259+
new AbortError(
260+
'App-specific webhook subscriptions are not supported when use_legacy_install_flow is enabled.',
261+
`To use app-specific webhooks, you need to:
262+
1. Remove 'use_legacy_install_flow = true' from your configuration
263+
2. Run 'shopify app deploy' to sync your scopes with the Partner Dashboard
264+
265+
Alternatively, continue using shop-specific webhooks with the legacy install flow.
266+
267+
Learn more: https://shopify.dev/docs/apps/build/authentication-authorization/app-installation`,
268+
),
269+
)
270+
})
271+
272+
test('does not throw an error when app-specific webhooks are used without legacy install flow', async () => {
273+
// Given
274+
const configuration: CurrentAppConfiguration = {
275+
...DEFAULT_CONFIG,
276+
access_scopes: {
277+
scopes: 'read_orders',
278+
use_legacy_install_flow: false,
279+
},
280+
webhooks: {
281+
api_version: '2024-07',
282+
subscriptions: [
283+
{
284+
topics: ['orders/create'],
285+
uri: 'webhooks',
286+
},
287+
],
288+
},
289+
}
290+
const app = testApp({configuration})
291+
292+
// When/Then
293+
await expect(app.preDeployValidation()).resolves.not.toThrow()
294+
})
295+
296+
test('does not throw an error when legacy install flow is enabled without app-specific webhooks', async () => {
297+
// Given
298+
const configuration: CurrentAppConfiguration = {
299+
...DEFAULT_CONFIG,
300+
access_scopes: {
301+
scopes: 'read_orders',
302+
use_legacy_install_flow: true,
303+
},
304+
webhooks: {
305+
api_version: '2024-07',
306+
},
307+
}
308+
const app = testApp({configuration})
309+
310+
// When/Then
311+
await expect(app.preDeployValidation()).resolves.not.toThrow()
312+
})
313+
314+
test('does not throw an error when neither app-specific webhooks nor legacy install flow are used', async () => {
315+
// Given
316+
const configuration: CurrentAppConfiguration = {
317+
...DEFAULT_CONFIG,
318+
access_scopes: {
319+
scopes: 'read_orders',
320+
},
321+
webhooks: {
322+
api_version: '2024-07',
323+
},
324+
}
325+
const app = testApp({configuration})
326+
327+
// When/Then
328+
await expect(app.preDeployValidation()).resolves.not.toThrow()
329+
})
330+
331+
test('does not throw an error for legacy schema apps', async () => {
332+
// Given
333+
const configuration: LegacyAppConfiguration = {
334+
...CORRECT_LEGACY_APP_SCHEMA,
335+
scopes: 'read_orders',
336+
}
337+
const app = testApp(configuration, 'legacy')
338+
339+
// When/Then
340+
await expect(app.preDeployValidation()).resolves.not.toThrow()
341+
})
342+
343+
test('handles null/undefined subscriptions safely', async () => {
344+
// Given
345+
const configuration: CurrentAppConfiguration = {
346+
...DEFAULT_CONFIG,
347+
access_scopes: {
348+
scopes: 'read_orders',
349+
use_legacy_install_flow: true,
350+
},
351+
webhooks: {
352+
api_version: '2024-07',
353+
// Testing edge case
354+
subscriptions: null as any,
355+
},
356+
}
357+
const app = testApp({configuration})
358+
359+
// When/Then
360+
await expect(app.preDeployValidation()).resolves.not.toThrow()
361+
})
362+
})
363+
235364
describe('validateFunctionExtensionsWithUiHandle', () => {
236365
const generateFunctionConfig = ({type, uiHandle}: {type?: string; uiHandle?: string}): FunctionConfigType => ({
237366
description: 'description',

packages/app/src/cli/models/app/app.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,8 @@ export class App<
457457
}
458458

459459
async preDeployValidation() {
460+
this.validateWebhookLegacyFlowCompatibility()
461+
460462
const functionExtensionsWithUiHandle = this.allExtensions.filter(
461463
(ext) => ext.isFunctionExtension && (ext.configuration as unknown as FunctionConfigType).ui?.handle,
462464
) as ExtensionInstance<FunctionConfigType>[]
@@ -568,6 +570,32 @@ export class App<
568570
this.configuration.auth.redirect_urls = devApplicationURLs.redirectUrlWhitelist
569571
}
570572
}
573+
574+
/**
575+
* Validates that app-specific webhooks are not used with legacy install flow.
576+
* This incompatibility exists because app-specific webhooks require declarative
577+
* scopes in the Partner Dashboard, which aren't synced when using legacy flow.
578+
* @throws When app-specific webhooks are used with legacy install flow
579+
*/
580+
private validateWebhookLegacyFlowCompatibility(): void {
581+
if (!isCurrentAppSchema(this.configuration)) return
582+
583+
const hasAppSpecificWebhooks = (this.configuration.webhooks?.subscriptions?.length ?? 0) > 0
584+
const usesLegacyInstallFlow = this.configuration.access_scopes?.use_legacy_install_flow === true
585+
586+
if (hasAppSpecificWebhooks && usesLegacyInstallFlow) {
587+
throw new AbortError(
588+
'App-specific webhook subscriptions are not supported when use_legacy_install_flow is enabled.',
589+
`To use app-specific webhooks, you need to:
590+
1. Remove 'use_legacy_install_flow = true' from your configuration
591+
2. Run 'shopify app deploy' to sync your scopes with the Partner Dashboard
592+
593+
Alternatively, continue using shop-specific webhooks with the legacy install flow.
594+
595+
Learn more: https://shopify.dev/docs/apps/build/authentication-authorization/app-installation`,
596+
)
597+
}
598+
}
571599
}
572600

573601
export function validateFunctionExtensionsWithUiHandle(

0 commit comments

Comments
 (0)