Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 31 additions & 27 deletions packages/features/insights/server/trpc-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ import {
routedToPerPeriodCsvInputSchema,
bookingRepositoryBaseInputSchema,
} from "@calcom/features/insights/server/raw-data.schema";
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
import type { PrismaClient } from "@calcom/prisma";
import type { Prisma } from "@calcom/prisma/client";
import { MembershipRole } from "@calcom/prisma/enums";
import authedProcedure from "@calcom/trpc/server/procedures/authedProcedure";
import { router } from "@calcom/trpc/server/trpc";

Expand Down Expand Up @@ -213,17 +215,8 @@ const userBelongsToTeamProcedure = authedProcedure.use(async ({ ctx, next, getRa
}
}

const membershipOrg = await ctx.insightsDb.membership.findFirst({
where: {
userId: ctx.user.id,
teamId: ctx.user.organizationId,
accepted: true,
role: {
in: ["OWNER", "ADMIN"],
},
},
});
if (!membershipOrg) {
const hasOrgAccess = await checkInsightsPermission(ctx.user.id, ctx.user.organizationId);
if (!hasOrgAccess) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
isOwnerAdminOfParentTeam = true;
Expand All @@ -247,6 +240,16 @@ const userSelect = {
avatarUrl: true,
};

async function checkInsightsPermission(userId: number, teamId: number): Promise<boolean> {
const permissionCheckService = new PermissionCheckService();
return await permissionCheckService.checkPermission({
userId,
teamId,
permission: "insights.read",
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
});
}

const emptyResponseBookingKPIStats = {
empty: true,
created: {
Expand Down Expand Up @@ -651,22 +654,29 @@ export const insightsRouter = router({
return result;
}

// Look if user it's admin/owner in multiple teams
// Get all team IDs where user has insights.read permission in a single optimized query
// This properly handles both PBAC permissions and traditional role-based access
const permissionCheckService = new PermissionCheckService();
const teamIdsWithAccess = await permissionCheckService.getTeamIdsWithPermission({
userId: user.id,
permission: "insights.read",
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
});

if (teamIdsWithAccess.length === 0) {
return [];
}

// Fetch only teams where user has both membership AND insights access
// This avoids fetching unnecessary team data by filtering at the database level
const belongsToTeams = await ctx.insightsDb.membership.findMany({
where: {
team: {
slug: { not: null },
},
accepted: true,
userId: user.id,
OR: [
{
role: "ADMIN",
},
{
role: "OWNER",
},
],
teamId: { in: teamIdsWithAccess },
},
include: {
team: {
Expand All @@ -681,13 +691,7 @@ export const insightsRouter = router({
},
});

if (belongsToTeams.length === 0) {
return [];
}

const result: IResultTeamList[] = belongsToTeams.map((membership) => {
return { ...membership.team };
});
const result: IResultTeamList[] = belongsToTeams.map((membership) => ({ ...membership.team }));

return result;
}),
Expand Down
Loading