diff --git a/integrations/dropbox/src/webhook-events/oauth/wizard.ts b/integrations/dropbox/src/webhook-events/oauth/wizard.ts index 53e92e40747..c90cd943900 100644 --- a/integrations/dropbox/src/webhook-events/oauth/wizard.ts +++ b/integrations/dropbox/src/webhook-events/oauth/wizard.ts @@ -65,7 +65,7 @@ const _redirectToDropboxHandler: WizardHandler = async (props) => { const _getOAuthRedirectUri = (ctx?: bp.Context) => oauthWizard.getWizardStepUrl('oauth-callback', ctx).toString() const _oauthCallbackHandler: WizardHandler = async (props) => { - const { responses, query, client, ctx, logger } = props + const { responses, query, client, ctx, logger, setIntegrationIdentifier } = props const oauthError = query.get('error') const oauthErrorDescription = query.get('error_description') @@ -100,7 +100,7 @@ const _oauthCallbackHandler: WizardHandler = async (props) => { await oauthClient.processAuthorizationCode(authorizationCode, redirectUri) logger.forBot().info('Successfully exchanged authorization code for refresh token') - await client.configureIntegration({ identifier: ctx.webhookId }) + setIntegrationIdentifier(ctx.webhookId) return responses.endWizard({ success: true, diff --git a/integrations/hubspot/src/webhook/handlers/oauth-wizard.ts b/integrations/hubspot/src/webhook/handlers/oauth-wizard.ts index e93ff7732bc..4e6e597c5b5 100644 --- a/integrations/hubspot/src/webhook/handlers/oauth-wizard.ts +++ b/integrations/hubspot/src/webhook/handlers/oauth-wizard.ts @@ -92,6 +92,7 @@ const _oauthCallbackStep: oauthWizard.WizardStepHandler = async logger, query, responses, + setIntegrationIdentifier, }) => { const error = query.get('error') if (error) { @@ -115,7 +116,7 @@ const _oauthCallbackStep: oauthWizard.WizardStepHandler = async const hsClient = new HubspotClient({ accessToken: credentials.accessToken, client, ctx, logger }) const hubId = await hsClient.getHubId() - await client.configureIntegration({ identifier: hubId }) + setIntegrationIdentifier(hubId) if (enableHitl) { return responses.redirectToStep('hitl-inbox-id') diff --git a/integrations/linear/src/handler.ts b/integrations/linear/src/handler.ts index 021165eec2f..0b021db4010 100644 --- a/integrations/linear/src/handler.ts +++ b/integrations/linear/src/handler.ts @@ -96,6 +96,7 @@ export const handler: bp.IntegrationProps['handler'] = async (props) => { userId: userId as string, // TODO: fix this }) } + return } const _safeCheckWebhookSignature = ({ diff --git a/integrations/linear/src/misc/linear.ts b/integrations/linear/src/misc/linear.ts index 7aa11ebd50b..09a7d8464a9 100644 --- a/integrations/linear/src/misc/linear.ts +++ b/integrations/linear/src/misc/linear.ts @@ -1,4 +1,4 @@ -import { RuntimeError, z } from '@botpress/sdk' +import { OAUTH_IDENTIFIER_HEADER, Response, RuntimeError, z } from '@botpress/sdk' import { LinearClient } from '@linear/sdk' import axios from 'axios' import queryString from 'query-string' @@ -269,7 +269,7 @@ export const registerWebhook = async ({ logger.forBot().info('Linear webhook registered successfully.') } -export const handleOauth = async ({ req, ctx, client, logger }: bp.HandlerProps) => { +export const handleOauth = async ({ req, ctx, client, logger }: bp.HandlerProps): Promise => { const linearOauthClient = new LinearOauthClient() const query = queryString.parse(req.query) @@ -290,7 +290,7 @@ export const handleOauth = async ({ req, ctx, client, logger }: bp.HandlerProps) const linearClient = new LinearClient({ accessToken: credentials.accessToken }) const organization = await linearClient.organization - await client.configureIntegration({ identifier: organization.id, scheduleRegisterCall: 'monthly' }) + await client.configureIntegration({ scheduleRegisterCall: 'monthly' }) const webhookUrl = `${process.env.BP_WEBHOOK_URL}/${ctx.webhookId}` try { @@ -299,4 +299,9 @@ export const handleOauth = async ({ req, ctx, client, logger }: bp.HandlerProps) const errorMessage = thrown instanceof Error ? thrown.message : String(thrown) logger.forBot().warn('Failed to register webhook:', errorMessage) } + + return { + status: 200, + headers: { [OAUTH_IDENTIFIER_HEADER]: organization.id }, + } } diff --git a/integrations/messenger/src/webhook/handlers/oauth/wizard.ts b/integrations/messenger/src/webhook/handlers/oauth/wizard.ts index 0c8fa6dd2ac..191539bdbac 100644 --- a/integrations/messenger/src/webhook/handlers/oauth/wizard.ts +++ b/integrations/messenger/src/webhook/handlers/oauth/wizard.ts @@ -126,7 +126,14 @@ const _selectPageHandler: WizardHandler = async ({ responses, client, ctx, logge }) } -const _setupHandler: WizardHandler = async ({ responses, client, ctx, logger, selectedChoice }) => { +const _setupHandler: WizardHandler = async ({ + responses, + client, + ctx, + logger, + selectedChoice, + setIntegrationIdentifier, +}) => { const { userToken } = await getMetaClientCredentials({ configType: 'oauth', client, ctx }).catch(() => ({ userToken: undefined, })) @@ -166,9 +173,7 @@ const _setupHandler: WizardHandler = async ({ responses, client, ctx, logger, se logger.forBot().info(`Successfully subscribed to webhooks for OAuth page ${pageId}`) - await client.configureIntegration({ - identifier: pageId, - }) + setIntegrationIdentifier(pageId) return responses.displayButtons({ pageTitle: 'Configuration Complete', diff --git a/integrations/sunco/src/wizard.ts b/integrations/sunco/src/wizard.ts index 8a5cf34ad80..c54b3f70e39 100644 --- a/integrations/sunco/src/wizard.ts +++ b/integrations/sunco/src/wizard.ts @@ -75,10 +75,10 @@ const _addToChannels: WizardHandler = async (props) => { } const _selectIdentifier: WizardHandler = async (props) => { - const { responses, client, ctx, selectedChoice } = props + const { responses, client, ctx, selectedChoice, setIntegrationIdentifier } = props if (selectedChoice) { - await client.configureIntegration({ identifier: selectedChoice }) + setIntegrationIdentifier(selectedChoice) return responses.redirectToStep('end') } diff --git a/integrations/whatsapp/src/webhook/handlers/oauth/wizard.ts b/integrations/whatsapp/src/webhook/handlers/oauth/wizard.ts index 36ead28efbb..b227ab01d46 100644 --- a/integrations/whatsapp/src/webhook/handlers/oauth/wizard.ts +++ b/integrations/whatsapp/src/webhook/handlers/oauth/wizard.ts @@ -202,7 +202,7 @@ const _doStepVerifyNumber = async ( } const _doStepWrapUp: WizardHandler = async (props) => { - const { responses, client, ctx, logger } = props + const { responses, client, ctx, logger, setIntegrationIdentifier } = props const credentials = await _getCredentialsState(client, ctx) const { accessToken, wabaId, defaultBotPhoneNumberId } = credentials if (!accessToken) { @@ -215,9 +215,7 @@ const _doStepWrapUp: WizardHandler = async (props) => { throw new Error(PHONE_NUMBER_ID_UNAVAILABLE_ERROR) } const oauthClient = new MetaOauthClient(logger) - await client.configureIntegration({ - identifier: wabaId, - }) + setIntegrationIdentifier(wabaId) await oauthClient.registerNumber(defaultBotPhoneNumberId, accessToken) await oauthClient.subscribeToWebhooks(wabaId, accessToken) diff --git a/integrations/zendesk/src/oauth/wizard.ts b/integrations/zendesk/src/oauth/wizard.ts index 386af2e983d..35f9464ff90 100644 --- a/integrations/zendesk/src/oauth/wizard.ts +++ b/integrations/zendesk/src/oauth/wizard.ts @@ -127,7 +127,7 @@ const _resetHandler: WizardHandler = async (props) => { } const _oauthCallbackHandler: WizardHandler = async (props) => { - const { responses, query, client, ctx } = props + const { responses, query, client, ctx, setIntegrationIdentifier } = props const authorizationCode = query.get('code') if (!authorizationCode) { return responses.endWizard({ @@ -146,7 +146,7 @@ const _oauthCallbackHandler: WizardHandler = async (props) => { const newCredentials = { ...credentials, accessToken } await _patchCredentialsState(client, ctx, newCredentials) - await client.configureIntegration({ identifier: ctx.webhookId }) + setIntegrationIdentifier(ctx.webhookId) return responses.redirectToStep('end') } diff --git a/packages/cli/package.json b/packages/cli/package.json index 725f1561ef9..29dc81ff9a8 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -28,7 +28,7 @@ "@apidevtools/json-schema-ref-parser": "^11.7.0", "@botpress/chat": "0.5.5", "@botpress/client": "1.44.0", - "@botpress/sdk": "6.9.1", + "@botpress/sdk": "6.9.2", "@bpinternal/const": "^0.1.0", "@bpinternal/tunnel": "^0.1.1", "@bpinternal/verel": "^0.2.0", diff --git a/packages/cli/templates/empty-bot/package.json b/packages/cli/templates/empty-bot/package.json index 2184275cf9f..dac2c254069 100644 --- a/packages/cli/templates/empty-bot/package.json +++ b/packages/cli/templates/empty-bot/package.json @@ -6,7 +6,7 @@ "private": true, "dependencies": { "@botpress/client": "1.44.0", - "@botpress/sdk": "6.9.1" + "@botpress/sdk": "6.9.2" }, "devDependencies": { "@types/node": "^22.16.4", diff --git a/packages/cli/templates/empty-integration/package.json b/packages/cli/templates/empty-integration/package.json index 0cf539568bd..0bfb224857e 100644 --- a/packages/cli/templates/empty-integration/package.json +++ b/packages/cli/templates/empty-integration/package.json @@ -7,7 +7,7 @@ "private": true, "dependencies": { "@botpress/client": "1.44.0", - "@botpress/sdk": "6.9.1" + "@botpress/sdk": "6.9.2" }, "devDependencies": { "@types/node": "^22.16.4", diff --git a/packages/cli/templates/empty-plugin/package.json b/packages/cli/templates/empty-plugin/package.json index e789e688542..73daed22f63 100644 --- a/packages/cli/templates/empty-plugin/package.json +++ b/packages/cli/templates/empty-plugin/package.json @@ -6,7 +6,7 @@ }, "private": true, "dependencies": { - "@botpress/sdk": "6.9.1" + "@botpress/sdk": "6.9.2" }, "devDependencies": { "@types/node": "^22.16.4", diff --git a/packages/cli/templates/hello-world/package.json b/packages/cli/templates/hello-world/package.json index 6b7c656b4ff..1039821dffd 100644 --- a/packages/cli/templates/hello-world/package.json +++ b/packages/cli/templates/hello-world/package.json @@ -7,7 +7,7 @@ "private": true, "dependencies": { "@botpress/client": "1.44.0", - "@botpress/sdk": "6.9.1" + "@botpress/sdk": "6.9.2" }, "devDependencies": { "@types/node": "^22.16.4", diff --git a/packages/cli/templates/webhook-message/package.json b/packages/cli/templates/webhook-message/package.json index fc01e118c21..a3020d7f67e 100644 --- a/packages/cli/templates/webhook-message/package.json +++ b/packages/cli/templates/webhook-message/package.json @@ -7,7 +7,7 @@ "private": true, "dependencies": { "@botpress/client": "1.44.0", - "@botpress/sdk": "6.9.1", + "@botpress/sdk": "6.9.2", "axios": "^1.6.8" }, "devDependencies": { diff --git a/packages/common/src/oauth-wizard/types.ts b/packages/common/src/oauth-wizard/types.ts index 14e3cd229ff..dfb56d1d513 100644 --- a/packages/common/src/oauth-wizard/types.ts +++ b/packages/common/src/oauth-wizard/types.ts @@ -19,6 +19,14 @@ export type WizardStepInputProps = { inputValue?: string formValues?: Record query: URLSearchParams + /** + * Records the integration identifier (e.g. an account or workspace id) by + * attaching the `x-bp-integration-identifier` response header to whatever + * Response this wizard step ultimately returns. The Botpress backend reads + * this header on the OAuth round-trip and treats it as proof that the + * integration owns the identifier, replacing any previous owner. + */ + setIntegrationIdentifier: (identifier: string) => void responses: { redirectToStep: (stepId: string) => Response redirectToExternalUrl: (url: string) => Response diff --git a/packages/common/src/oauth-wizard/wizard-handler.ts b/packages/common/src/oauth-wizard/wizard-handler.ts index 142f064f480..2908c215fab 100644 --- a/packages/common/src/oauth-wizard/wizard-handler.ts +++ b/packages/common/src/oauth-wizard/wizard-handler.ts @@ -61,7 +61,9 @@ export class OAuthWizard { } } - return await step.handler({ + const extraHeaders: Record = {} + + const response = await step.handler({ ...this._handlerProps, query: searchParams, selectedChoice: searchParams.get(consts.CHOICE_PARAM) ?? undefined, @@ -69,6 +71,9 @@ export class OAuthWizard { searchParams.getAll(consts.CHOICE_PARAM).length > 0 ? searchParams.getAll(consts.CHOICE_PARAM) : undefined, inputValue: searchParams.get(consts.INPUT_PARAM) ?? undefined, formValues: Object.keys(formValues).length > 0 ? formValues : undefined, + setIntegrationIdentifier(identifier: string) { + extraHeaders[sdk.OAUTH_IDENTIFIER_HEADER] = identifier + }, responses: { displayButtons: ({ buttons, pageTitle, htmlOrMarkdownPageContents }) => htmlDialogs.generateButtonDialog({ @@ -141,6 +146,14 @@ export class OAuthWizard { ), }, }) + + return { + ...response, + headers: { + ...response.headers, + ...extraHeaders, + }, + } } } diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 9070479a04d..1f317087439 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@botpress/sdk", - "version": "6.9.1", + "version": "6.9.2", "description": "Botpress SDK", "main": "./dist/index.cjs", "module": "./dist/index.mjs", diff --git a/packages/sdk/src/public-consts.ts b/packages/sdk/src/public-consts.ts index b1d5f2a0307..e247cf7ca98 100644 --- a/packages/sdk/src/public-consts.ts +++ b/packages/sdk/src/public-consts.ts @@ -4,3 +4,5 @@ export const WELL_KNOWN_ATTRIBUTES = { HIDDEN_IN_STUDIO: { bpActionHiddenInStudio: 'true' }, AWAIT_RETURN: { bpActionAwaitReturn: 'true' }, } as const + +export const OAUTH_IDENTIFIER_HEADER = 'x-bp-integration-identifier' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 561130f0f1a..3b52034f43c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2670,7 +2670,7 @@ importers: specifier: 1.44.0 version: link:../client '@botpress/sdk': - specifier: 6.9.1 + specifier: 6.9.2 version: link:../sdk '@bpinternal/const': specifier: ^0.1.0 @@ -2794,7 +2794,7 @@ importers: specifier: 1.44.0 version: link:../../../client '@botpress/sdk': - specifier: 6.9.1 + specifier: 6.9.2 version: link:../../../sdk devDependencies: '@types/node': @@ -2810,7 +2810,7 @@ importers: specifier: 1.44.0 version: link:../../../client '@botpress/sdk': - specifier: 6.9.1 + specifier: 6.9.2 version: link:../../../sdk devDependencies: '@types/node': @@ -2823,7 +2823,7 @@ importers: packages/cli/templates/empty-plugin: dependencies: '@botpress/sdk': - specifier: 6.9.1 + specifier: 6.9.2 version: link:../../../sdk devDependencies: '@types/node': @@ -2839,7 +2839,7 @@ importers: specifier: 1.44.0 version: link:../../../client '@botpress/sdk': - specifier: 6.9.1 + specifier: 6.9.2 version: link:../../../sdk devDependencies: '@types/node': @@ -2855,7 +2855,7 @@ importers: specifier: 1.44.0 version: link:../../../client '@botpress/sdk': - specifier: 6.9.1 + specifier: 6.9.2 version: link:../../../sdk axios: specifier: ^1.6.8