Skip to content

Commit

Permalink
Add the OrgId to all webhooks API calls
Browse files Browse the repository at this point in the history
The Organization ID is required by the new API to check the various token permissions
  • Loading branch information
cdarne committed Nov 26, 2024
1 parent ebca8ac commit fe1b92e
Show file tree
Hide file tree
Showing 20 changed files with 129 additions and 61 deletions.
1 change: 1 addition & 0 deletions packages/app/src/cli/commands/app/webhook/trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export default class WebhookTrigger extends AppCommand {
clientSecret: flags['client-secret'] || flags['shared-secret'],
path: flags.path,
config: flags.config,
organizationId: appContextResult.organization.id,
}

await webhookTriggerService(usedFlags)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export async function setupDevProcesses({
webs: reloadedApp.webs,
backendPort: network.backendPort,
frontendPort: network.frontendPort,
organizationId: remoteApp.organizationId,
developerPlatformClient,
storeFqdn,
apiSecret,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface SendWebhookOptions {
storeFqdn: string
apiSecret: string
webhooksPath: string
organizationId: string
}

export interface SendWebhookProcess extends BaseProcess<SendWebhookOptions> {
Expand All @@ -23,6 +24,7 @@ export const sendWebhook: DevProcessFunction<SendWebhookOptions> = async ({stdou
address: `http://localhost:${options.deliveryPort}${options.webhooksPath}`,
sharedSecret: options.apiSecret,
storeFqdn: options.storeFqdn,
organizationId: options.organizationId,
})
}

Expand All @@ -31,12 +33,14 @@ export function setupSendUninstallWebhookProcess({
remoteAppUpdated,
backendPort,
frontendPort,
organizationId,
...options
}: Pick<SendWebhookOptions, 'developerPlatformClient' | 'storeFqdn' | 'apiSecret'> & {
remoteAppUpdated: boolean
backendPort: number
frontendPort: number
webs: Web[]
organizationId: string
}): SendWebhookProcess | undefined {
const {backendConfig, frontendConfig} = frontAndBackendConfig(webs)
const webhooksPath =
Expand All @@ -52,6 +56,7 @@ export function setupSendUninstallWebhookProcess({
options: {
deliveryPort: backendConfig ? backendPort : frontendPort,
webhooksPath,
organizationId,
...options,
},
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {describe, expect, test} from 'vitest'
describe('requestApiVersions', () => {
test('calls partners to request data and returns ordered array', async () => {
// Given - When
const got = await requestApiVersions(testDeveloperPlatformClient())
const organizationId = 'organizationId'
const got = await requestApiVersions(testDeveloperPlatformClient(), organizationId)

// Then
expect(got).toEqual(['2023', '2022', 'unstable'])
Expand Down
8 changes: 6 additions & 2 deletions packages/app/src/cli/services/webhook/request-api-versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ export const GetApiVersionsQuery = `
* Requests available api-versions in order to validate flags or present a list of options
*
* @param developerPlatformClient - The client to access the platform API
* @param organizationId - Organization ID required by the API to verify permissions
* @returns List of public api-versions
*/
export async function requestApiVersions(developerPlatformClient: DeveloperPlatformClient): Promise<string[]> {
const {publicApiVersions: result}: PublicApiVersionsSchema = await developerPlatformClient.apiVersions()
export async function requestApiVersions(
developerPlatformClient: DeveloperPlatformClient,
organizationId: string,
): Promise<string[]> {
const {publicApiVersions: result}: PublicApiVersionsSchema = await developerPlatformClient.apiVersions(organizationId)

const unstableIdx = result.indexOf('unstable')
if (unstableIdx === -1) {
Expand Down
6 changes: 4 additions & 2 deletions packages/app/src/cli/services/webhook/request-sample.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ const inputValues: SendSampleWebhookVariables = {
address: 'https://example.org',
shared_secret: 'A_SECRET',
}
const organizationId = 'organizationId'

describe('getWebhookSample', () => {
test('calls partners to request data without api-key', async () => {
// Given/When
const got = await getWebhookSample(testDeveloperPlatformClient(), inputValues)
const got = await getWebhookSample(testDeveloperPlatformClient(), inputValues, organizationId)

// Then
expect(got.samplePayload).toEqual('{ "sampleField": "SampleValue" }')
Expand All @@ -29,7 +31,7 @@ describe('getWebhookSample', () => {
}

// When
const got = await getWebhookSample(testDeveloperPlatformClient(), variables)
const got = await getWebhookSample(testDeveloperPlatformClient(), variables, organizationId)

// Then
expect(got.samplePayload).toEqual('{ "sampleField": "SampleValue" }')
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/cli/services/webhook/request-sample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,17 @@ export const sendSampleWebhookMutation = `
* - address - A destination for the webhook notification
* - shared_secret - A secret to generate the HMAC header apps can use to validate the origin
* - api_key - Client Api Key required to validate Event-Bridge addresses (optional)
* @param organizationId - Organization ID required by the API to verify permissions
* @returns Empty if a remote delivery was requested, payload data if a local delivery was requested
*/
export async function getWebhookSample(
developerPlatformClient: DeveloperPlatformClient,
variables: SendSampleWebhookVariables,
organizationId: string,
): Promise<SampleWebhook> {
const {sendSampleWebhook: result}: SendSampleWebhookSchema = await developerPlatformClient.sendSampleWebhook(
variables,
organizationId,
)

return result
Expand Down
3 changes: 2 additions & 1 deletion packages/app/src/cli/services/webhook/request-topics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import {testDeveloperPlatformClient} from '../../models/app/app.test-data.js'
import {describe, expect, test} from 'vitest'

const aVersion = 'SOME_VERSION'
const anOrganizationId = 'organizationId'

describe('requestTopics', () => {
test('calls partners to request topics data and returns array', async () => {
// Given/When
const got = await requestTopics(testDeveloperPlatformClient(), aVersion)
const got = await requestTopics(testDeveloperPlatformClient(), aVersion, anOrganizationId)

// Then
expect(got).toEqual(['orders/create', 'shop/redact'])
Expand Down
4 changes: 3 additions & 1 deletion packages/app/src/cli/services/webhook/request-topics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ export const getTopicsQuery = `
*
* @param developerPlatformClient - The client to access the platform API
* @param apiVersion - ApiVersion of the topics
* @param organizationId - Organization ID required by the API to verify permissions
* @returns - Available webhook topics for the api-version
*/
export async function requestTopics(
developerPlatformClient: DeveloperPlatformClient,
apiVersion: string,
organizationId: string,
): Promise<string[]> {
const variables: WebhookTopicsVariables = {api_version: apiVersion}
const {webhookTopics: result}: WebhookTopicsSchema = await developerPlatformClient.topics(variables)
const {webhookTopics: result}: WebhookTopicsSchema = await developerPlatformClient.topics(variables, organizationId)

return result
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ vi.mock('@shopify/cli-kit/node/system')

const address = 'http://localhost:3000/test/path'
const storeFqdn = 'test-store.myshopify.io'
const organizationId = 'organizationId'

describe('sendUninstallWebhookToAppServer', () => {
test('requests sample and API versions, triggers local webhook', async () => {
Expand All @@ -22,6 +23,7 @@ describe('sendUninstallWebhookToAppServer', () => {
sharedSecret: 'sharedSecret',
storeFqdn,
developerPlatformClient: testDeveloperPlatformClient(),
organizationId,
})

expect(result).toBe(true)
Expand All @@ -45,6 +47,7 @@ describe('sendUninstallWebhookToAppServer', () => {
sharedSecret: 'sharedSecret',
storeFqdn,
developerPlatformClient,
organizationId,
})

expect(result).toBe(false)
Expand All @@ -68,6 +71,7 @@ describe('sendUninstallWebhookToAppServer', () => {
sharedSecret: 'sharedSecret',
storeFqdn,
developerPlatformClient,
organizationId,
})

expect(result).toBe(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ interface SendUninstallWebhookToAppServerOptions {
storeFqdn: string
address: string
sharedSecret: string
organizationId: string
}

export async function sendUninstallWebhookToAppServer(
options: SendUninstallWebhookToAppServerOptions,
): Promise<boolean> {
const apiVersions = await requestApiVersions(options.developerPlatformClient)
const apiVersions = await requestApiVersions(options.developerPlatformClient, options.organizationId)
const variables: SendSampleWebhookVariables = {
topic: 'app/uninstalled',
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
Expand All @@ -27,7 +28,7 @@ export async function sendUninstallWebhookToAppServer(
delivery_method: DELIVERY_METHOD.LOCALHOST,
shared_secret: options.sharedSecret,
}
const sample = await getWebhookSample(options.developerPlatformClient, variables)
const sample = await getWebhookSample(options.developerPlatformClient, variables, options.organizationId)

options.stdout.write('Sending APP_UNINSTALLED webhook to app server')

Expand Down
18 changes: 13 additions & 5 deletions packages/app/src/cli/services/webhook/trigger-options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const API_KEY = 'AN_API_KEY'
const APP = testAppLinked()
const ORGANIZATION_APP = testOrganizationApp()
const developerPlatformClient = testDeveloperPlatformClient()
const organizationId = ORGANIZATION_APP.organizationId

afterEach(() => {
mockAndCaptureOutput().clear()
Expand All @@ -30,7 +31,7 @@ describe('collectApiVersion', () => {
vi.mocked(apiVersionPrompt)

// When
const version = await collectApiVersion(developerPlatformClient, '2023-01')
const version = await collectApiVersion(developerPlatformClient, '2023-01', organizationId)

// Then
expect(version).toEqual('2023-01')
Expand All @@ -43,7 +44,7 @@ describe('collectApiVersion', () => {
vi.mocked(requestApiVersions).mockResolvedValue(['2023-01', 'unstable'])

// When
const version = await collectApiVersion(developerPlatformClient, undefined)
const version = await collectApiVersion(developerPlatformClient, undefined, organizationId)

// Then
expect(version).toEqual('2023-01')
Expand All @@ -59,7 +60,12 @@ describe('collectTopic', () => {
vi.mocked(requestTopics).mockResolvedValue(['shop/redact', 'orders/create'])

// When
const method = await collectTopic(developerPlatformClient, '2023-01', 'shop/redact')
const method = await collectTopic(
developerPlatformClient,
'2023-01',
'shop/redact',
ORGANIZATION_APP.organizationId,
)

// Then
expect(method).toEqual('shop/redact')
Expand All @@ -72,7 +78,9 @@ describe('collectTopic', () => {
vi.mocked(requestTopics).mockResolvedValue(['shop/redact', 'orders/create'])

// When then
await expect(collectTopic(developerPlatformClient, '2023-01', 'unknown/topic')).rejects.toThrow(AbortError)
await expect(collectTopic(developerPlatformClient, '2023-01', 'unknown/topic', organizationId)).rejects.toThrow(
AbortError,
)
expect(topicPrompt).toHaveBeenCalledTimes(0)
})

Expand All @@ -82,7 +90,7 @@ describe('collectTopic', () => {
vi.mocked(requestTopics).mockResolvedValue(['shop/redact', 'orders/create'])

// When
const topic = await collectTopic(developerPlatformClient, 'unstable', undefined)
const topic = await collectTopic(developerPlatformClient, 'unstable', undefined, organizationId)

// Then
expect(topic).toEqual('orders/create')
Expand Down
11 changes: 7 additions & 4 deletions packages/app/src/cli/services/webhook/trigger-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,15 @@ export async function collectCredentials(
*
* @param developerPlatformClient - The client to access the platform API
* @param apiVersion - VALID or undefined api-version
* @param organizationId - Organization ID required by the API to verify permissions
* @returns api-version
*/
export async function collectApiVersion(
developerPlatformClient: DeveloperPlatformClient,
apiVersion: string | undefined,
organizationId: string,
): Promise<string> {
const apiVersions = await requestApiVersions(developerPlatformClient)
const apiVersions = await requestApiVersions(developerPlatformClient, organizationId)
if (apiVersion) return parseApiVersionFlag(apiVersion, apiVersions)
return apiVersionPrompt(apiVersions)
}
Expand All @@ -76,18 +78,19 @@ export async function collectApiVersion(
* @param developerPlatformClient - The client to access the platform API
* @param apiVersion - VALID api-version
* @param topic - topic or undefined
* @param organizationId - Organization ID required by the API to verify permissions
* @returns topic
*/
export async function collectTopic(
developerPlatformClient: DeveloperPlatformClient,
apiVersion: string,
topic: string | undefined,
organizationId: string,
): Promise<string> {
const topics = await requestTopics(developerPlatformClient, apiVersion, organizationId)
if (topic) {
return parseTopicFlag(topic, apiVersion, await requestTopics(developerPlatformClient, apiVersion))
return parseTopicFlag(topic, apiVersion, topics)
}

const topics = await requestTopics(developerPlatformClient, apiVersion)
return topicPrompt(topics)
}

Expand Down
Loading

0 comments on commit fe1b92e

Please sign in to comment.