From b8530e2d78b8f09cbf58d99978bf5e7a3dc1caba Mon Sep 17 00:00:00 2001 From: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> Date: Tue, 18 Nov 2025 00:00:42 +0200 Subject: [PATCH] fix: delegation credential error webhooks + refactor repeated code (#25232) * fix: delegation credential error webhooks * refactor: extract repeated delegation credential error webhook logic into helper methods - Added private triggerDelegationCredentialError method in Office365CalendarService class - Added triggerDelegationCredentialError helper function in TeamsVideoApiAdapter - Replaced all 4 instances in Office365CalendarService with helper method call - Replaced all 4 instances in TeamsVideoApiAdapter with helper function call - Keeps code DRY by eliminating repeated if statement and webhook trigger logic Co-Authored-By: morgan@cal.com --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../googlecalendar/lib/CalendarAuth.ts | 4 +- .../office365calendar/lib/CalendarService.ts | 87 ++++++------------- .../office365video/lib/VideoApiAdapter.ts | 82 +++++------------ packages/features/webhooks/lib/dto/types.ts | 2 + .../lib/repository/WebhookRepository.ts | 30 +++++++ ...triggerDelegationCredentialErrorWebhook.ts | 23 +++-- 6 files changed, 99 insertions(+), 129 deletions(-) diff --git a/packages/app-store/googlecalendar/lib/CalendarAuth.ts b/packages/app-store/googlecalendar/lib/CalendarAuth.ts index df2d343c4e..6ead261afb 100644 --- a/packages/app-store/googlecalendar/lib/CalendarAuth.ts +++ b/packages/app-store/googlecalendar/lib/CalendarAuth.ts @@ -140,7 +140,7 @@ export class CalendarAuth { delegationError = new CalendarAppDelegationCredentialError("Error authorizing delegation credential"); } - if (user && user.email && this.credential.appId) { + if (user && user.email && this.credential.appId && this.credential.delegatedToId) { await triggerDelegationCredentialErrorWebhook({ error: delegationError, credential: { @@ -152,7 +152,7 @@ export class CalendarAuth { id: this.credential.userId ?? 0, email: user.email, }, - orgId: this.credential.teamId, + delegationCredentialId: this.credential.delegatedToId, }); } diff --git a/packages/app-store/office365calendar/lib/CalendarService.ts b/packages/app-store/office365calendar/lib/CalendarService.ts index 5bc69b34ab..b61095c762 100644 --- a/packages/app-store/office365calendar/lib/CalendarService.ts +++ b/packages/app-store/office365calendar/lib/CalendarService.ts @@ -124,6 +124,29 @@ export default class Office365CalendarService implements Calendar { this.log = logger.getSubLogger({ prefix: [`[[lib] ${this.integrationName}`] }); } + private async triggerDelegationCredentialError(error: Error): Promise { + if ( + this.credential.userId && + this.credential.user && + this.credential.appId && + this.credential.delegatedToId + ) { + await triggerDelegationCredentialErrorWebhook({ + error, + credential: { + id: this.credential.id, + type: this.credential.type, + appId: this.credential.appId, + }, + user: { + id: this.credential.userId, + email: this.credential.user.email, + }, + delegationCredentialId: this.credential.delegatedToId, + }); + } + } + private async getAuthUrl(delegatedTo: boolean, tenantId?: string): Promise { if (delegatedTo) { if (!tenantId) { @@ -131,21 +154,7 @@ export default class Office365CalendarService implements Calendar { "Invalid DelegationCredential Settings: tenantId is missing" ); - if (this.credential.userId && this.credential.user && this.credential.appId) { - await triggerDelegationCredentialErrorWebhook({ - error, - credential: { - id: this.credential.id, - type: this.credential.type, - appId: this.credential.appId, - }, - user: { - id: this.credential.userId, - email: this.credential.user.email, - }, - orgId: this.credential.teamId, - }); - } + await this.triggerDelegationCredentialError(error); throw error; } @@ -165,21 +174,7 @@ export default class Office365CalendarService implements Calendar { "Delegation credential without clientId or Secret" ); - if (this.credential.userId && this.credential.user && this.credential.appId) { - await triggerDelegationCredentialErrorWebhook({ - error, - credential: { - id: this.credential.id, - type: this.credential.type, - appId: this.credential.appId, - }, - user: { - id: this.credential.userId, - email: this.credential.user.email, - }, - orgId: this.credential.teamId, - }); - } + await this.triggerDelegationCredentialError(error); throw error; } @@ -207,21 +202,7 @@ export default class Office365CalendarService implements Calendar { "Delegation credential without clientId or Secret" ); - if (this.credential.userId && this.credential.user && this.credential.appId) { - await triggerDelegationCredentialErrorWebhook({ - error, - credential: { - id: this.credential.id, - type: this.credential.type, - appId: this.credential.appId, - }, - user: { - id: this.credential.userId, - email: this.credential.user.email, - }, - orgId: this.credential.teamId, - }); - } + await this.triggerDelegationCredentialError(error); throw error; } @@ -260,21 +241,7 @@ export default class Office365CalendarService implements Calendar { "User might not exist in Microsoft Azure Active Directory" ); - if (this.credential.userId && this.credential.user && this.credential.appId) { - await triggerDelegationCredentialErrorWebhook({ - error, - credential: { - id: this.credential.id, - type: this.credential.type, - appId: this.credential.appId, - }, - user: { - id: this.credential.userId ?? 0, - email: this.credential.user.email, - }, - orgId: this.credential.teamId, - }); - } + await this.triggerDelegationCredentialError(error); throw error; } diff --git a/packages/app-store/office365video/lib/VideoApiAdapter.ts b/packages/app-store/office365video/lib/VideoApiAdapter.ts index f2f7c35b33..4206e0ee7b 100644 --- a/packages/app-store/office365video/lib/VideoApiAdapter.ts +++ b/packages/app-store/office365video/lib/VideoApiAdapter.ts @@ -42,6 +42,24 @@ const TeamsVideoApiAdapter = (credential: CredentialForCalendarServiceWithTenant let azureUserId: string | null; const tokenResponse = oAuthManagerHelper.getTokenObjectFromCredential(credential); + async function triggerDelegationCredentialError(error: Error): Promise { + if (credential.userId && credential.user && credential.appId && credential.delegatedToId) { + await triggerDelegationCredentialErrorWebhook({ + error, + credential: { + id: credential.id, + type: credential.type, + appId: credential.appId, + }, + user: { + id: credential.userId ?? 0, + email: credential.user.email, + }, + delegationCredentialId: credential.delegatedToId, + }); + } + } + const auth = new OAuthManager({ credentialSyncVariables: oAuthManagerHelper.credentialSyncVariables, resourceOwner: { @@ -68,21 +86,7 @@ const TeamsVideoApiAdapter = (credential: CredentialForCalendarServiceWithTenant "Delegation credential without clientId or Secret" ); - if (credential.userId && credential.user && credential.appId) { - await triggerDelegationCredentialErrorWebhook({ - error, - credential: { - id: credential.id, - type: credential.type, - appId: credential.appId, - }, - user: { - id: credential.userId ?? 0, - email: credential.user.email, - }, - orgId: credential.teamId, - }); - } + await triggerDelegationCredentialError(error); throw error; } @@ -130,21 +134,7 @@ const TeamsVideoApiAdapter = (credential: CredentialForCalendarServiceWithTenant "Invalid DelegationCredential Settings: tenantId is missing" ); - if (credential.userId && credential.user && credential.appId) { - await triggerDelegationCredentialErrorWebhook({ - error, - credential: { - id: credential.id, - type: credential.type, - appId: credential.appId, - }, - user: { - id: credential.userId ?? 0, - email: credential.user.email, - }, - orgId: credential.teamId, - }); - } + await triggerDelegationCredentialError(error); throw error; } @@ -179,21 +169,7 @@ const TeamsVideoApiAdapter = (credential: CredentialForCalendarServiceWithTenant "Delegation credential without clientId or Secret" ); - if (credential.userId && credential.user && credential.appId) { - await triggerDelegationCredentialErrorWebhook({ - error, - credential: { - id: credential.id, - type: credential.type, - appId: credential.appId, - }, - user: { - id: credential.userId ?? 0, - email: credential.user.email, - }, - orgId: credential.teamId, - }); - } + await triggerDelegationCredentialError(error); throw error; } @@ -231,21 +207,7 @@ const TeamsVideoApiAdapter = (credential: CredentialForCalendarServiceWithTenant "User might not exist in Microsoft Azure Active Directory" ); - if (credential.userId && credential.user && credential.appId) { - await triggerDelegationCredentialErrorWebhook({ - error, - credential: { - id: credential.id, - type: credential.type, - appId: credential.appId, - }, - user: { - id: credential.userId ?? 0, - email: credential.user.email, - }, - orgId: credential.teamId, - }); - } + await triggerDelegationCredentialError(error); throw error; } diff --git a/packages/features/webhooks/lib/dto/types.ts b/packages/features/webhooks/lib/dto/types.ts index c7286ad924..d6dd0a5f2f 100644 --- a/packages/features/webhooks/lib/dto/types.ts +++ b/packages/features/webhooks/lib/dto/types.ts @@ -498,10 +498,12 @@ export type DelegationCredentialErrorPayloadType = { id: number; type: string; appId: string; + delegationCredentialId?: string; }; user: { id: number; email: string; + organizationId?: number; }; }; diff --git a/packages/features/webhooks/lib/repository/WebhookRepository.ts b/packages/features/webhooks/lib/repository/WebhookRepository.ts index a777cc3fa2..b24be4bd0f 100644 --- a/packages/features/webhooks/lib/repository/WebhookRepository.ts +++ b/packages/features/webhooks/lib/repository/WebhookRepository.ts @@ -261,6 +261,36 @@ export class WebhookRepository implements IWebhookRepository { }); } + async findByOrgIdAndTrigger({ + orgId, + triggerEvent, + }: { + orgId: number; + triggerEvent: WebhookTriggerEvents; + }): Promise { + return await this.prisma.webhook.findMany({ + where: { + teamId: orgId, + platform: false, + eventTriggers: { has: triggerEvent }, + }, + select: { + id: true, + subscriberUrl: true, + payloadTemplate: true, + active: true, + eventTriggers: true, + secret: true, + teamId: true, + userId: true, + platform: true, + time: true, + timeUnit: true, + appId: true, + }, + }); + } + async getFilteredWebhooksForUser({ userId, userRole }: { userId: number; userRole?: UserPermissionRole }) { const user = await this.prisma.user.findUnique({ where: { id: userId }, diff --git a/packages/features/webhooks/lib/triggerDelegationCredentialErrorWebhook.ts b/packages/features/webhooks/lib/triggerDelegationCredentialErrorWebhook.ts index c42bc4d944..0f63a808d8 100644 --- a/packages/features/webhooks/lib/triggerDelegationCredentialErrorWebhook.ts +++ b/packages/features/webhooks/lib/triggerDelegationCredentialErrorWebhook.ts @@ -1,3 +1,4 @@ +import { DelegationCredentialRepository } from "@calcom/features/delegation-credentials/repositories/DelegationCredentialRepository"; import { DelegationCredentialErrorPayloadType } from "@calcom/features/webhooks/lib/dto/types"; import type { CalendarAppDelegationCredentialError } from "@calcom/lib/CalendarAppError"; import logger from "@calcom/lib/logger"; @@ -15,24 +16,30 @@ export async function triggerDelegationCredentialErrorWebhook(params: { error: CalendarAppDelegationCredentialError; credential: DelegationCredentialErrorPayloadType["credential"]; user: DelegationCredentialErrorPayloadType["user"]; - orgId?: number | null; + delegationCredentialId: string; }): Promise { try { - const { error, credential, user, orgId } = params; + const { error, credential, user, delegationCredentialId } = params; - if (!orgId) { - log.debug("Skipping webhook emission - no organization context"); + const delegationCredential = await DelegationCredentialRepository.findById({ + id: delegationCredentialId, + }); + + if (!delegationCredential) { + log.debug("No delegation credential found", { delegationCredentialId }); return; } const webhookRepository = WebhookRepository.getInstance(); - const webhooks = await webhookRepository.getSubscribers({ - teamId: orgId, + const webhooks = await webhookRepository.findByOrgIdAndTrigger({ + orgId: delegationCredential.organizationId, triggerEvent: WebhookTriggerEvents.DELEGATION_CREDENTIAL_ERROR, }); if (webhooks.length === 0) { - log.debug("No webhooks subscribed to DELEGATION_CREDENTIAL_ERROR for organization", { orgId }); + log.debug("No webhooks subscribed to DELEGATION_CREDENTIAL_ERROR for organization", { + orgId: delegationCredential.organizationId, + }); return; } @@ -45,10 +52,12 @@ export async function triggerDelegationCredentialErrorWebhook(params: { id: credential.id, type: credential.type, appId: credential.appId || "unknown", + delegationCredentialId: delegationCredential.id, }, user: { id: user.id, email: user.email, + organizationId: delegationCredential.organizationId, }, } satisfies DelegationCredentialErrorPayloadType;