Skip to content

Commit

Permalink
Merge pull request #3872 from activepieces/feat/selfhost-appwebhooks
Browse files Browse the repository at this point in the history
feat: add support for app webhooks
  • Loading branch information
abuaboud committed Feb 9, 2024
2 parents f572127 + e06e148 commit 4c194f5
Show file tree
Hide file tree
Showing 8 changed files with 64 additions and 17 deletions.
25 changes: 25 additions & 0 deletions docs/install/configurations/setup-app-webhooks.mdx
Original file line number Diff line number Diff line change
@@ -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).
3 changes: 2 additions & 1 deletion docs/mint.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 7 additions & 1 deletion packages/backend/src/app/flags/flag.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`
}
Expand Down
14 changes: 8 additions & 6 deletions packages/backend/src/app/helper/secret-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> | undefined = undefined
let webhookSecrets: Record<string, { webhookSecret: string }> | undefined = undefined

export function getEdition(): ApEdition {
const edition = system.get<ApEdition>(SystemProp.EDITION)
Expand All @@ -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<Record<string, string>> {
const currentEdition = getEdition()
if (currentEdition !== ApEdition.CLOUD) {
async function getWebhookSecrets(): Promise<Record<string, {
webhookSecret: string
}>> {
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)
}
2 changes: 1 addition & 1 deletion packages/backend/src/app/helper/system/system-prop.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand Down Expand Up @@ -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',
}
1 change: 1 addition & 0 deletions packages/shared/src/lib/flag/flag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
9 changes: 9 additions & 0 deletions packages/ui/common/src/lib/service/flag.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ export class FlagService {
})
);
}

getArrayFlag(flag: ApFlagId): Observable<string[]> {
return this.getAllFlags().pipe(
map((value) => {
return value[flag] as string[];
})
);
}

getThirdPartyProvidersMap() {
return this.getAllFlags().pipe(
map((res) => {
Expand Down
19 changes: 11 additions & 8 deletions packages/ui/feature-pieces/src/lib/services/piece-meta.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
AddPieceRequestBody,
PieceOptionRequest,
TriggerType,
ApFlagId,
PieceScope,
} from '@activepieces/shared';
import { HttpClient } from '@angular/common/http';
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand All @@ -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),
};
})
);
Expand Down

0 comments on commit 4c194f5

Please sign in to comment.