diff --git a/docs/install/configurations/setup-app-webhooks.mdx b/docs/install/configurations/setup-app-webhooks.mdx new file mode 100644 index 0000000000..d1b9399e79 --- /dev/null +++ b/docs/install/configurations/setup-app-webhooks.mdx @@ -0,0 +1,25 @@ +--- +title: "Setup Webhook Secrets" +description: "" +--- + +Certain apps like Slack and Square only support one webhook per OAuth2 app. This means that manual configuration is required in their developer portal, and it cannot be automated. + +## Slack + +**Configure Webhook Secret** + +1. Visit the "Basic Information" section of your Slack OAuth settings. +2. Copy the "Signing Secret" and save it. +3. Set the following environment variable in your activepieces environment: + ``` + AP_APP_WEBHOOK_SECRETS={"@activepieces/piece-slack": {"webhookSecret": "SIGNING_SECRET"}} + ``` +4. Restart your application instance. + + +**Configure Webhook URL** + +1. Go to the "Event Subscription" settings in the Slack OAuth2 developer platform. +2. The URL format should be: `https://YOUR_AP_INSTANCE/api/v1/app-events/slack`. +3. When connecting to Slack, use your OAuth2 credentials or update the OAuth2 app details from the admin console (in platform plans). diff --git a/docs/mint.json b/docs/mint.json index ae0011cc06..2a3afe4aee 100755 --- a/docs/mint.json +++ b/docs/mint.json @@ -170,7 +170,8 @@ "icon": "gear", "pages": [ "install/configurations/environment-variables", - "install/configurations/error-notifications" + "install/configurations/error-notifications", + "install/configurations/setup-app-webhooks" ] }, "install/setup-ssl", diff --git a/packages/backend/src/app/flags/flag.service.ts b/packages/backend/src/app/flags/flag.service.ts index ad14360a78..e3ccbdef54 100755 --- a/packages/backend/src/app/flags/flag.service.ts +++ b/packages/backend/src/app/flags/flag.service.ts @@ -210,12 +210,18 @@ export const flagService = { created, updated, }, + { + id: ApFlagId.SUPPORTED_APP_WEBHOOKS, + value: Object.keys(system.get(SystemProp.APP_WEBHOOK_SECRETS) ?? {}), + created, + updated, + }, ) return flags }, getThirdPartyRedirectUrl(platformId: string | undefined, hostname: string | undefined): string { - const isCustomerPlatform = platformId && !flagService.isCloudPlatform(platformId) + const isCustomerPlatform = platformId && !flagService.isCloudPlatform(platformId) if (isCustomerPlatform) { return `https://${hostname}/redirect` } diff --git a/packages/backend/src/app/helper/secret-helper.ts b/packages/backend/src/app/helper/secret-helper.ts index 317e497f8d..4e31097c86 100644 --- a/packages/backend/src/app/helper/secret-helper.ts +++ b/packages/backend/src/app/helper/secret-helper.ts @@ -2,7 +2,7 @@ import { ApEdition, FlowVersion, isNil } from '@activepieces/shared' import { system } from './system/system' import { SystemProp } from './system/system-prop' -let webhookSecrets: Record | undefined = undefined +let webhookSecrets: Record | undefined = undefined export function getEdition(): ApEdition { const edition = system.get(SystemProp.EDITION) @@ -24,13 +24,15 @@ export async function getWebhookSecret( if (webhookSecrets === undefined) { webhookSecrets = await getWebhookSecrets() } - return webhookSecrets[appName] + return webhookSecrets[appName].webhookSecret } -async function getWebhookSecrets(): Promise> { - const currentEdition = getEdition() - if (currentEdition !== ApEdition.CLOUD) { +async function getWebhookSecrets(): Promise> { + const appSecret = system.get(SystemProp.APP_WEBHOOK_SECRETS) + if (isNil(appSecret)) { return {} } - return JSON.parse(system.getOrThrow(SystemProp.APP_WEBHOOK_SECRETS)) + return JSON.parse(appSecret) } diff --git a/packages/backend/src/app/helper/system/system-prop.ts b/packages/backend/src/app/helper/system/system-prop.ts index 5ceba0138b..1dd25f1b79 100644 --- a/packages/backend/src/app/helper/system/system-prop.ts +++ b/packages/backend/src/app/helper/system/system-prop.ts @@ -1,5 +1,6 @@ export enum SystemProp { API_KEY = 'API_KEY', + APP_WEBHOOK_SECRETS = 'APP_WEBHOOK_SECRETS', API_RATE_LIMIT_AUTHN_ENABLED = 'API_RATE_LIMIT_AUTHN_ENABLED', API_RATE_LIMIT_AUTHN_MAX = 'API_RATE_LIMIT_AUTHN_MAX', API_RATE_LIMIT_AUTHN_WINDOW = 'API_RATE_LIMIT_AUTHN_WINDOW', @@ -75,6 +76,5 @@ export enum SystemProp { STRIPE_WEBHOOK_SECRET = 'STRIPE_WEBHOOK_SECRET', // CLOUD_ONLY - APP_WEBHOOK_SECRETS = 'APP_WEBHOOK_SECRETS', CLOUD_PLATFORM_ID = 'CLOUD_PLATFORM_ID', } diff --git a/packages/shared/src/lib/flag/flag.ts b/packages/shared/src/lib/flag/flag.ts index deec45ca96..320d4b5d4e 100755 --- a/packages/shared/src/lib/flag/flag.ts +++ b/packages/shared/src/lib/flag/flag.ts @@ -50,6 +50,7 @@ export enum ApFlagId { USER_CREATED = 'USER_CREATED', SHOW_GIT_SYNC = 'SHOW_GIT_SYNC', WEBHOOK_URL_PREFIX = 'WEBHOOK_URL_PREFIX', + SUPPORTED_APP_WEBHOOKS = 'SUPPORTED_APP_WEBHOOKS', SHOW_POWERED_BY_AP = 'SHOW_POWERED_BY_AP', PRIVACY_POLICY_URL = 'PRIVACY_POLICY_URL', TERMS_OF_SERVICE_URL = 'TERMS_OF_SERVICE_URL', diff --git a/packages/ui/common/src/lib/service/flag.service.ts b/packages/ui/common/src/lib/service/flag.service.ts index c12f08374e..7df3b40453 100644 --- a/packages/ui/common/src/lib/service/flag.service.ts +++ b/packages/ui/common/src/lib/service/flag.service.ts @@ -37,6 +37,15 @@ export class FlagService { }) ); } + + getArrayFlag(flag: ApFlagId): Observable { + return this.getAllFlags().pipe( + map((value) => { + return value[flag] as string[]; + }) + ); + } + getThirdPartyProvidersMap() { return this.getAllFlags().pipe( map((res) => { diff --git a/packages/ui/feature-pieces/src/lib/services/piece-meta.service.ts b/packages/ui/feature-pieces/src/lib/services/piece-meta.service.ts index aba8c173c7..873633c055 100644 --- a/packages/ui/feature-pieces/src/lib/services/piece-meta.service.ts +++ b/packages/ui/feature-pieces/src/lib/services/piece-meta.service.ts @@ -5,6 +5,7 @@ import { AddPieceRequestBody, PieceOptionRequest, TriggerType, + ApFlagId, PieceScope, } from '@activepieces/shared'; import { HttpClient } from '@angular/common/http'; @@ -111,17 +112,16 @@ export class PieceMetadataService { return `${pieceName}-${pieceVersion}`; } private filterAppWebhooks( + pieceName: string, triggersMap: TriggersMetadata, - edition: ApEdition + supportedApps: string[] ): TriggersMetadata { - if (edition !== ApEdition.COMMUNITY) { - return triggersMap; - } const triggersList = Object.entries(triggersMap); const filteredTriggersList = triggersList.filter( - ([, trigger]) => trigger.type !== TriggerStrategy.APP_WEBHOOK + ([, trigger]) => trigger.type !== TriggerStrategy.APP_WEBHOOK || + supportedApps.includes(pieceName) ); return Object.fromEntries(filteredTriggersList); @@ -218,9 +218,12 @@ export class PieceMetadataService { return this.piecesCache.get(cacheKey)!; } - const pieceMetadata$ = this.edition$.pipe( + const pieceMetadata$ = combineLatest({ + edition: this.edition$, + supportedApps: this.flagsService.getArrayFlag(ApFlagId.SUPPORTED_APP_WEBHOOKS), + }).pipe( take(1), - switchMap((edition) => { + switchMap(({ edition, supportedApps }) => { return this.fetchPieceMetadata({ pieceName, pieceVersion, @@ -230,7 +233,7 @@ export class PieceMetadataService { map((pieceMetadata) => { return { ...pieceMetadata, - triggers: this.filterAppWebhooks(pieceMetadata.triggers, edition), + triggers: this.filterAppWebhooks(pieceMetadata.name, pieceMetadata.triggers, supportedApps), }; }) );