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 <morgan@cal.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Morgan
2025-11-18 00:00:42 +02:00
committed by GitHub
co-authored by morgan@cal.com Devin AI
parent 7f1b94ad19
commit b8530e2d78
6 changed files with 99 additions and 129 deletions
@@ -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,
});
}
@@ -124,6 +124,29 @@ export default class Office365CalendarService implements Calendar {
this.log = logger.getSubLogger({ prefix: [`[[lib] ${this.integrationName}`] });
}
private async triggerDelegationCredentialError(error: Error): Promise<void> {
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<string> {
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;
}
@@ -42,6 +42,24 @@ const TeamsVideoApiAdapter = (credential: CredentialForCalendarServiceWithTenant
let azureUserId: string | null;
const tokenResponse = oAuthManagerHelper.getTokenObjectFromCredential(credential);
async function triggerDelegationCredentialError(error: Error): Promise<void> {
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;
}
@@ -498,10 +498,12 @@ export type DelegationCredentialErrorPayloadType = {
id: number;
type: string;
appId: string;
delegationCredentialId?: string;
};
user: {
id: number;
email: string;
organizationId?: number;
};
};
@@ -261,6 +261,36 @@ export class WebhookRepository implements IWebhookRepository {
});
}
async findByOrgIdAndTrigger({
orgId,
triggerEvent,
}: {
orgId: number;
triggerEvent: WebhookTriggerEvents;
}): Promise<WebhookSubscriber[]> {
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 },
@@ -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<void> {
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;