From 6ab2f2596b13070232693853b3ef946e97805e3d Mon Sep 17 00:00:00 2001 From: Mohammad AbuAboud Date: Wed, 13 Dec 2023 13:38:54 +0000 Subject: [PATCH 1/2] feat: add app webhooks for self hosted --- packages/backend/src/app/flags/flag.service.ts | 8 +++++++- packages/backend/src/app/helper/secret-helper.ts | 14 ++++++++------ .../backend/src/app/helper/system/system-prop.ts | 2 +- packages/shared/src/lib/flag/flag.ts | 1 + .../src/lib/services/piece-meta.service.ts | 14 +++++++++----- 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/packages/backend/src/app/flags/flag.service.ts b/packages/backend/src/app/flags/flag.service.ts index 326e7697a4..9db8b10502 100755 --- a/packages/backend/src/app/flags/flag.service.ts +++ b/packages/backend/src/app/flags/flag.service.ts @@ -1,4 +1,4 @@ -import { ApEdition, ApFlagId, Flag } from '@activepieces/shared' +import { ApEdition, ApFlagId, Flag, isNil } from '@activepieces/shared' import { databaseConnection } from '../database/database-connection' import { system } from '../helper/system/system' import { SystemProp } from '../helper/system/system-prop' @@ -194,6 +194,12 @@ export const flagService = { created, updated, }, + { + id: ApFlagId.APP_WEBHOOK_ENABLED, + value: !isNil(system.get(SystemProp.APP_WEBHOOK_SECRETS)), + created, + updated, + }, ) return flags 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 1c78936df6..bd08b8b612 100644 --- a/packages/backend/src/app/helper/system/system-prop.ts +++ b/packages/backend/src/app/helper/system/system-prop.ts @@ -1,6 +1,6 @@ export enum SystemProp { API_KEY = 'API_KEY', - APP_WEBHOOK_SECRETS = 'APP_WEBHOOK_SECRETS', // Only used in cloud edition + APP_WEBHOOK_SECRETS = 'APP_WEBHOOK_SECRETS', CACHE_PATH = 'CACHE_PATH', CLOUD_AUTH_ENABLED = 'CLOUD_AUTH_ENABLED', CHATBOT_ENABLED = 'CHATBOT_ENABLED', diff --git a/packages/shared/src/lib/flag/flag.ts b/packages/shared/src/lib/flag/flag.ts index ba9cb779bc..2f08e7c15e 100755 --- a/packages/shared/src/lib/flag/flag.ts +++ b/packages/shared/src/lib/flag/flag.ts @@ -47,6 +47,7 @@ export enum ApFlagId { THEME = 'THEME', USER_CREATED = 'USER_CREATED', WEBHOOK_URL_PREFIX = 'WEBHOOK_URL_PREFIX', + APP_WEBHOOK_ENABLED = 'APP_WEBHOOK_ENABLED', 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/feature-pieces/src/lib/services/piece-meta.service.ts b/packages/ui/feature-pieces/src/lib/services/piece-meta.service.ts index 92e86125b0..a8720ec7e3 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, } from '@activepieces/shared'; import { HttpClient } from '@angular/common/http'; import { @@ -109,9 +110,9 @@ export class PieceMetadataService { } private filterAppWebhooks( triggersMap: TriggersMetadata, - edition: ApEdition + appWebhookEnabled: boolean ): TriggersMetadata { - if (edition !== ApEdition.COMMUNITY) { + if (!appWebhookEnabled) { return triggersMap; } @@ -214,9 +215,12 @@ export class PieceMetadataService { return this.piecesCache.get(cacheKey)!; } - const pieceMetadata$ = this.edition$.pipe( + const pieceMetadata$ = combineLatest({ + edition: this.edition$, + appWebhookEnabled: this.flagsService.isFlagEnabled(ApFlagId.APP_WEBHOOK_ENABLED), + }).pipe( take(1), - switchMap((edition) => { + switchMap(({ edition, appWebhookEnabled }) => { return this.fetchPieceMetadata({ pieceName, pieceVersion, @@ -226,7 +230,7 @@ export class PieceMetadataService { map((pieceMetadata) => { return { ...pieceMetadata, - triggers: this.filterAppWebhooks(pieceMetadata.triggers, edition), + triggers: this.filterAppWebhooks(pieceMetadata.triggers, appWebhookEnabled), }; }) ); From e06e1483f72ffeecfb9257e8c87c1fb0d428c948 Mon Sep 17 00:00:00 2001 From: Mohammad AbuAboud Date: Fri, 9 Feb 2024 11:42:38 +0000 Subject: [PATCH 2/2] docs: add tutorial for webhook secrets --- .../configurations/setup-app-webhooks.mdx | 25 +++++++++++++++++++ docs/mint.json | 3 ++- .../backend/src/app/flags/flag.service.ts | 6 ++--- packages/shared/src/lib/flag/flag.ts | 2 +- .../ui/common/src/lib/service/flag.service.ts | 9 +++++++ .../src/lib/services/piece-meta.service.ts | 15 ++++++----- 6 files changed, 47 insertions(+), 13 deletions(-) create mode 100644 docs/install/configurations/setup-app-webhooks.mdx 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 fe79c25979..e3ccbdef54 100755 --- a/packages/backend/src/app/flags/flag.service.ts +++ b/packages/backend/src/app/flags/flag.service.ts @@ -211,8 +211,8 @@ export const flagService = { updated, }, { - id: ApFlagId.APP_WEBHOOK_ENABLED, - value: !isNil(system.get(SystemProp.APP_WEBHOOK_SECRETS)), + id: ApFlagId.SUPPORTED_APP_WEBHOOKS, + value: Object.keys(system.get(SystemProp.APP_WEBHOOK_SECRETS) ?? {}), created, updated, }, @@ -221,7 +221,7 @@ export const flagService = { 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/shared/src/lib/flag/flag.ts b/packages/shared/src/lib/flag/flag.ts index 187b29f093..320d4b5d4e 100755 --- a/packages/shared/src/lib/flag/flag.ts +++ b/packages/shared/src/lib/flag/flag.ts @@ -50,7 +50,7 @@ export enum ApFlagId { USER_CREATED = 'USER_CREATED', SHOW_GIT_SYNC = 'SHOW_GIT_SYNC', WEBHOOK_URL_PREFIX = 'WEBHOOK_URL_PREFIX', - APP_WEBHOOK_ENABLED = 'APP_WEBHOOK_ENABLED', + 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 a29bc0c8ad..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 @@ -112,17 +112,16 @@ export class PieceMetadataService { return `${pieceName}-${pieceVersion}`; } private filterAppWebhooks( + pieceName: string, triggersMap: TriggersMetadata, - appWebhookEnabled: boolean + supportedApps: string[] ): TriggersMetadata { - if (!appWebhookEnabled) { - 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); @@ -221,10 +220,10 @@ export class PieceMetadataService { const pieceMetadata$ = combineLatest({ edition: this.edition$, - appWebhookEnabled: this.flagsService.isFlagEnabled(ApFlagId.APP_WEBHOOK_ENABLED), + supportedApps: this.flagsService.getArrayFlag(ApFlagId.SUPPORTED_APP_WEBHOOKS), }).pipe( take(1), - switchMap(({ edition, appWebhookEnabled }) => { + switchMap(({ edition, supportedApps }) => { return this.fetchPieceMetadata({ pieceName, pieceVersion, @@ -234,7 +233,7 @@ export class PieceMetadataService { map((pieceMetadata) => { return { ...pieceMetadata, - triggers: this.filterAppWebhooks(pieceMetadata.triggers, appWebhookEnabled), + triggers: this.filterAppWebhooks(pieceMetadata.name, pieceMetadata.triggers, supportedApps), }; }) );