diff --git a/packages/trpc/server/routers/viewer/organizations/listOtherTeamMembers.handler.ts b/packages/trpc/server/routers/viewer/organizations/listOtherTeamMembers.handler.ts index 82e2d7ff33..062fbd1d3c 100644 --- a/packages/trpc/server/routers/viewer/organizations/listOtherTeamMembers.handler.ts +++ b/packages/trpc/server/routers/viewer/organizations/listOtherTeamMembers.handler.ts @@ -1,9 +1,10 @@ import z from "zod"; import { getBookerBaseUrlSync } from "@calcom/features/ee/organizations/lib/getBookerBaseUrlSync"; +import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service"; import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; import { prisma } from "@calcom/prisma"; -import type { Prisma } from "@calcom/prisma/client"; +import { MembershipRole, type Prisma } from "@calcom/prisma/client"; import { TRPCError } from "@trpc/server"; @@ -26,7 +27,7 @@ type ListOptions = { input: TListOtherTeamMembersSchema; }; -export const listOtherTeamMembers = async ({ input }: ListOptions) => { +export const listOtherTeamMembers = async ({ ctx, input }: ListOptions) => { const whereConditional: Prisma.MembershipWhereInput = { teamId: input.teamId, }; @@ -76,6 +77,28 @@ export const listOtherTeamMembers = async ({ input }: ListOptions) => { }); } + if (!team.parentId) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Team does not belong to an organization", + }); + } + + const permissionCheckService = new PermissionCheckService(); + const hasPermission = await permissionCheckService.checkPermission({ + userId: ctx.user.id, + teamId: team.parentId, + permission: "team.listMembers", + fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN], + }); + + if (!hasPermission) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "You are not authorized to view team members in this organization", + }); + } + const members = await prisma.membership.findMany({ where: whereConditional, select: { diff --git a/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.test.ts b/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.test.ts index 944137be51..c99ec31bb2 100644 --- a/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.test.ts +++ b/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.test.ts @@ -41,7 +41,34 @@ vi.mock("@calcom/prisma", () => { }; }); +// Mock PBAC dependencies - these need to be mocked before PermissionCheckService +vi.mock("@calcom/features/pbac/infrastructure/repositories/PermissionRepository"); +vi.mock("@calcom/features/flags/features.repository"); +vi.mock("@calcom/features/membership/repositories/MembershipRepository"); +vi.mock("@calcom/features/pbac/services/permission.service", () => { + return { + PermissionService: vi.fn().mockImplementation(() => ({ + validatePermission: vi.fn().mockReturnValue({ isValid: true }), + validatePermissions: vi.fn().mockReturnValue({ isValid: true }), + })), + }; +}); + +vi.mock("@calcom/features/pbac/services/permission-check.service", () => { + return { + PermissionCheckService: vi.fn().mockImplementation(() => ({ + checkPermission: vi.fn().mockResolvedValue(true), + checkPermissions: vi.fn().mockResolvedValue(true), + getUserPermissions: vi.fn().mockResolvedValue([]), + getResourcePermissions: vi.fn().mockResolvedValue([]), + getTeamIdsWithPermission: vi.fn().mockResolvedValue([]), + getTeamIdsWithPermissions: vi.fn().mockResolvedValue([]), + })), + }; +}); + function fakeNoUsersFoundMatchingInvitations(args: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any team: any; invitations: { role: MembershipRole; diff --git a/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.ts b/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.ts index d23f3cf3e6..55f3629376 100644 --- a/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.ts @@ -1,6 +1,7 @@ import { type TFunction } from "i18next"; import { getTeamBillingServiceFactory } from "@calcom/ee/billing/di/containers/Billing"; +import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service"; import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError"; import logger from "@calcom/lib/logger"; @@ -247,6 +248,22 @@ const inviteMembers = async ({ ctx, input }: InviteMemberOptions) => { const { usernameOrEmail, role, isPlatform, creationSource } = input; const team = await getTeamOrThrow(input.teamId); + + const permissionCheckService = new PermissionCheckService(); + const hasPermission = await permissionCheckService.checkPermission({ + userId: ctx.user.id, + teamId: team.id, + permission: "team.invite", + fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN], + }); + + if (!hasPermission) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "You are not authorized to invite team members in this organization's team", + }); + } + const requestedSlugForTeam = team?.metadata?.requestedSlug ?? null; const isTeamAnOrg = team.isOrganization; const organization = inviter.profile.organization;