diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index cfc01741f..e43e02f37 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -44,6 +44,7 @@ import { TSecretSyncRaw, TUpdateSecretSyncDTO } from "@app/services/secret-sync/secret-sync-types"; +import { TWebhookPayloads } from "@app/services/webhook/webhook-types"; import { WorkflowIntegration } from "@app/services/workflow-integration/workflow-integration-types"; import { KmipPermission } from "../kmip/kmip-enum"; @@ -206,6 +207,7 @@ export enum EventType { CREATE_WEBHOOK = "create-webhook", UPDATE_WEBHOOK_STATUS = "update-webhook-status", DELETE_WEBHOOK = "delete-webhook", + WEBHOOK_TRIGGERED = "webhook-triggered", GET_SECRET_IMPORTS = "get-secret-imports", GET_SECRET_IMPORT = "get-secret-import", CREATE_SECRET_IMPORT = "create-secret-import", @@ -1440,6 +1442,14 @@ interface DeleteWebhookEvent { }; } +export interface WebhookTriggeredEvent { + type: EventType.WEBHOOK_TRIGGERED; + metadata: { + webhookId: string; + status: string; + } & TWebhookPayloads; +} + interface GetSecretImportsEvent { type: EventType.GET_SECRET_IMPORTS; metadata: { @@ -3221,6 +3231,7 @@ export type Event = | CreateWebhookEvent | UpdateWebhookStatusEvent | DeleteWebhookEvent + | WebhookTriggeredEvent | GetSecretImportsEvent | GetSecretImportEvent | CreateSecretImportEvent diff --git a/backend/src/services/secret/secret-queue.ts b/backend/src/services/secret/secret-queue.ts index 714df0d3f..6c9cf64d3 100644 --- a/backend/src/services/secret/secret-queue.ts +++ b/backend/src/services/secret/secret-queue.ts @@ -1581,6 +1581,7 @@ export const secretQueueFactory = ({ projectDAL, webhookDAL, event: job.data, + auditLogService, secretManagerDecryptor: (value) => secretManagerDecryptor({ cipherTextBlob: value }).toString() }); }); diff --git a/backend/src/services/webhook/webhook-fns.ts b/backend/src/services/webhook/webhook-fns.ts index a16158e14..6729e9a37 100644 --- a/backend/src/services/webhook/webhook-fns.ts +++ b/backend/src/services/webhook/webhook-fns.ts @@ -4,9 +4,12 @@ import { AxiosError } from "axios"; import picomatch from "picomatch"; import { TWebhooks } from "@app/db/schemas"; +import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service"; +import { EventType, WebhookTriggeredEvent } from "@app/ee/services/audit-log/audit-log-types"; import { request } from "@app/lib/config/request"; import { NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; +import { ActorType } from "@app/services/auth/auth-type"; import { TProjectDALFactory } from "../project/project-dal"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; @@ -163,6 +166,7 @@ export type TFnTriggerWebhookDTO = { projectEnvDAL: Pick; projectDAL: Pick; secretManagerDecryptor: (value: Buffer) => string; + auditLogService: Pick; }; // this is reusable function @@ -175,7 +179,8 @@ export const fnTriggerWebhook = async ({ projectEnvDAL, event, secretManagerDecryptor, - projectDAL + projectDAL, + auditLogService }: TFnTriggerWebhookDTO) => { const webhooks = await webhookDAL.findAllWebhooks(projectId, environment); const toBeTriggeredHooks = webhooks.filter( @@ -200,16 +205,43 @@ export const fnTriggerWebhook = async ({ }) ); + const eventPayloads: WebhookTriggeredEvent["metadata"][] = []; // filter hooks by status const successWebhooks = webhooksTriggered .filter(({ status }) => status === "fulfilled") - .map((_, i) => toBeTriggeredHooks[i].id); + .map((_, i) => { + eventPayloads.push({ + webhookId: toBeTriggeredHooks[i].id, + type: event.type, + payload: { + type: toBeTriggeredHooks[i].type!, + ...event.payload, + projectName + }, + status: "success" + } as WebhookTriggeredEvent["metadata"]); + + return toBeTriggeredHooks[i].id; + }); const failedWebhooks = webhooksTriggered .filter(({ status }) => status === "rejected") - .map((data, i) => ({ - id: toBeTriggeredHooks[i].id, - error: data.status === "rejected" ? (data.reason as AxiosError).message : "" - })); + .map((data, i) => { + eventPayloads.push({ + webhookId: toBeTriggeredHooks[i].id, + type: event.type, + payload: { + type: toBeTriggeredHooks[i].type!, + ...event.payload, + projectName + }, + status: "failed" + } as WebhookTriggeredEvent["metadata"]); + + return { + id: toBeTriggeredHooks[i].id, + error: data.status === "rejected" ? (data.reason as AxiosError).message : "" + }; + }); await webhookDAL.transaction(async (tx) => { const env = await projectEnvDAL.findOne({ projectId, slug: environment }, tx); @@ -236,5 +268,21 @@ export const fnTriggerWebhook = async ({ ); } }); + + for (const eventPayload of eventPayloads) { + // eslint-disable-next-line no-await-in-loop + await auditLogService.createAuditLog({ + actor: { + type: ActorType.PLATFORM, + metadata: {} + }, + projectId, + event: { + type: EventType.WEBHOOK_TRIGGERED, + metadata: eventPayload + } + }); + } + logger.info({ environment, secretPath, projectId }, "Secret webhook job ended"); }; diff --git a/docs/documentation/platform/webhooks.mdx b/docs/documentation/platform/webhooks.mdx index 92d3ff8b8..e6e71b9b0 100644 --- a/docs/documentation/platform/webhooks.mdx +++ b/docs/documentation/platform/webhooks.mdx @@ -27,7 +27,7 @@ If the signature in the header matches the signature that you generated, then yo ```json { - "event": "secret.modified", + "event": "secrets.modified", "project": { "workspaceId": "the workspace id", "environment": "project environment", diff --git a/frontend/src/hooks/api/auditLogs/constants.tsx b/frontend/src/hooks/api/auditLogs/constants.tsx index ca5a38e0b..696f5745f 100644 --- a/frontend/src/hooks/api/auditLogs/constants.tsx +++ b/frontend/src/hooks/api/auditLogs/constants.tsx @@ -52,6 +52,7 @@ export const eventToNameMap: { [K in EventType]: string } = { [EventType.CREATE_WEBHOOK]: "Create webhook", [EventType.UPDATE_WEBHOOK_STATUS]: "Update webhook status", [EventType.DELETE_WEBHOOK]: "Delete webhook", + [EventType.WEBHOOK_TRIGGERED]: "Webhook event", [EventType.GET_SECRET_IMPORTS]: "List secret imports", [EventType.CREATE_SECRET_IMPORT]: "Create secret import", [EventType.UPDATE_SECRET_IMPORT]: "Update secret import", diff --git a/frontend/src/hooks/api/auditLogs/enums.tsx b/frontend/src/hooks/api/auditLogs/enums.tsx index 67f0bac99..159a61808 100644 --- a/frontend/src/hooks/api/auditLogs/enums.tsx +++ b/frontend/src/hooks/api/auditLogs/enums.tsx @@ -65,6 +65,7 @@ export enum EventType { CREATE_WEBHOOK = "create-webhook", UPDATE_WEBHOOK_STATUS = "update-webhook-status", DELETE_WEBHOOK = "delete-webhook", + WEBHOOK_TRIGGERED = "webhook-triggered", GET_SECRET_IMPORTS = "get-secret-imports", CREATE_SECRET_IMPORT = "create-secret-import", UPDATE_SECRET_IMPORT = "update-secret-import", diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/WebhooksTab/WebhooksTab.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/WebhooksTab/WebhooksTab.tsx index 035a0eaa1..8c8d75f47 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/WebhooksTab/WebhooksTab.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/WebhooksTab/WebhooksTab.tsx @@ -194,6 +194,7 @@ export const WebhooksTab = withProjectPermission( {url} +

{id}

{environment.slug} {secretPath}