Files
calendar/packages/lib/server/repository/webhook.ts
T
sean-brydonandGitHub b17eab3aeb chore: Use enums instead of hardcoded strings (#24132)
* Use enums instead of hardcoded strings

* use membership role
2025-09-29 10:57:36 +01:00

195 lines
5.0 KiB
TypeScript

import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
import { prisma } from "@calcom/prisma";
import type { Webhook } from "@calcom/prisma/client";
import { UserPermissionRole } from "@calcom/prisma/enums";
import { MembershipRole } from "@calcom/prisma/enums";
type WebhookGroup = {
teamId?: number | null;
profile: {
slug: string | null;
name: string | null;
image?: string;
};
metadata?: {
canModify: boolean;
canDelete: boolean;
};
webhooks: Webhook[];
};
const filterWebhooks = (webhook: Webhook) => {
const appIds = [
"zapier",
"make",
// Add more if needed
];
return !appIds.some((appId: string) => webhook.appId == appId);
};
export class WebhookRepository {
static async findByWebhookId(webhookId?: string) {
return await prisma.webhook.findUniqueOrThrow({
where: {
id: webhookId,
},
select: {
id: true,
subscriberUrl: true,
payloadTemplate: true,
active: true,
eventTriggers: true,
secret: true,
teamId: true,
userId: true,
platform: true,
time: true,
timeUnit: true,
},
});
}
static async getFilteredWebhooksForUser({
userId,
userRole,
}: {
userId: number;
userRole?: UserPermissionRole;
}) {
const user = await prisma.user.findUnique({
where: { id: userId },
select: {
id: true,
username: true,
name: true,
avatarUrl: true,
webhooks: true,
teams: {
where: {
accepted: true,
},
select: {
role: true,
team: {
select: {
id: true,
name: true,
slug: true,
logoUrl: true,
webhooks: true,
},
},
},
},
},
});
if (!user) {
throw new Error("User not found");
}
// Use permission service which handles both PBAC and role-based fallbacks
const permissionService = new PermissionCheckService();
// Build webhook groups with proper permissions
const webhookGroups: WebhookGroup[] = [];
// Add user's personal webhooks
webhookGroups.push({
teamId: null,
profile: {
slug: user.username,
name: user.name,
image: getUserAvatarUrl({ avatarUrl: user.avatarUrl }),
},
webhooks: user.webhooks.filter(filterWebhooks),
metadata: {
canModify: true,
canDelete: true,
},
});
// Check permissions for each team
// The permission service handles PBAC when enabled and falls back to role-based permissions
for (const membership of user.teams) {
const teamId = membership.team.id;
// Check read permission (fallback: MEMBER, ADMIN, OWNER can read)
const canRead = await permissionService.checkPermission({
userId,
teamId,
permission: "webhook.read",
fallbackRoles: [MembershipRole.MEMBER, MembershipRole.ADMIN, MembershipRole.OWNER],
});
if (!canRead) {
// User doesn't have permission to view this team's webhooks
continue;
}
// Check update/delete permissions in parallel (fallback: only ADMIN, OWNER can modify)
const [canUpdate, canDelete] = await Promise.all([
permissionService.checkPermission({
userId,
teamId,
permission: "webhook.update",
fallbackRoles: [MembershipRole.ADMIN, MembershipRole.OWNER],
}),
permissionService.checkPermission({
userId,
teamId,
permission: "webhook.delete",
fallbackRoles: [MembershipRole.ADMIN, MembershipRole.OWNER],
}),
]);
webhookGroups.push({
teamId: membership.team.id,
profile: {
name: membership.team.name,
slug: membership.team.slug || null,
image: getPlaceholderAvatar(membership.team.logoUrl, membership.team.name),
},
webhooks: membership.team.webhooks.filter(filterWebhooks),
metadata: {
canModify: canUpdate,
canDelete,
},
});
}
// Add platform webhooks for admins
if (userRole === UserPermissionRole.ADMIN) {
const platformWebhooks = await prisma.webhook.findMany({
where: { platform: true },
});
webhookGroups.push({
teamId: null,
profile: {
slug: "Platform",
name: "Platform",
image: getPlaceholderAvatar(null, "Platform"),
},
webhooks: platformWebhooks,
metadata: {
canDelete: true,
canModify: true,
},
});
}
return {
webhookGroups: webhookGroups.filter((group) => group.webhooks.length > 0),
profiles: webhookGroups.map((group) => ({
teamId: group.teamId,
...group.profile,
...group.metadata,
})),
};
}
}