From 0a59c95b93f189ee98b47f0d94522a6cb2206f76 Mon Sep 17 00:00:00 2001 From: sean-brydon <55134778+sean-brydon@users.noreply.github.com> Date: Mon, 30 Oct 2023 15:20:48 +0000 Subject: [PATCH 1/2] fix: impersonation for orgs (#12113) --- .../lib/ImpersonationProvider.ts | 103 ++++++++++++------ .../ee/teams/components/MemberListItem.tsx | 2 +- .../UserTable/ImpersonationMemberModal.tsx | 2 +- 3 files changed, 69 insertions(+), 38 deletions(-) diff --git a/packages/features/ee/impersonation/lib/ImpersonationProvider.ts b/packages/features/ee/impersonation/lib/ImpersonationProvider.ts index 9df72b7113..21cd67bc44 100644 --- a/packages/features/ee/impersonation/lib/ImpersonationProvider.ts +++ b/packages/features/ee/impersonation/lib/ImpersonationProvider.ts @@ -5,6 +5,7 @@ import { z } from "zod"; import { getSession } from "@calcom/features/auth/lib/getSession"; import prisma from "@calcom/prisma"; +import type { Prisma } from "@calcom/prisma/client"; const teamIdschema = z.object({ teamId: z.preprocess((a) => parseInt(z.string().parse(a), 10), z.number().positive()), @@ -63,11 +64,75 @@ export function checkUserIdentifier(creds: Partial) { } export function checkPermission(session: Session | null) { - if (session?.user.role !== "ADMIN" && process.env.NEXT_PUBLIC_TEAM_IMPERSONATION === "false") { + if ( + (session?.user.role !== "ADMIN" && process.env.NEXT_PUBLIC_TEAM_IMPERSONATION === "false") || + !session?.user + ) { throw new Error("You do not have permission to do this."); } } +async function getImpersonatedUser({ + session, + teamId, + creds, +}: { + session: Session | null; + teamId: number | undefined; + creds: Credentials | null; +}) { + let TeamWhereClause: Prisma.MembershipWhereInput = { + disableImpersonation: false, // Ensure they have impersonation enabled + accepted: true, // Ensure they are apart of the team and not just invited. + team: { + id: teamId, // Bring back only the right team + }, + }; + + // If you are an admin we dont need to follow this flow -> We can just follow the usual flow + // If orgId and teamId are the same we can follow the same flow + if (session?.user.org?.id && session.user.org.id !== teamId && session?.user.role !== "ADMIN") { + TeamWhereClause = { + disableImpersonation: false, + accepted: true, + team: { + id: session.user.org.id, + }, + }; + } + + // Get user who is being impersonated + const impersonatedUser = await prisma.user.findFirst({ + where: { + OR: [{ username: creds?.username }, { email: creds?.username }], + }, + select: { + id: true, + username: true, + role: true, + name: true, + email: true, + organizationId: true, + disableImpersonation: true, + locale: true, + teams: { + where: TeamWhereClause, + select: { + teamId: true, + disableImpersonation: true, + role: true, + }, + }, + }, + }); + + if (!impersonatedUser) { + throw new Error("This user does not exist"); + } + + return impersonatedUser; +} + const ImpersonationProvider = CredentialsProvider({ id: "impersonation-auth", name: "Impersonation", @@ -85,41 +150,7 @@ const ImpersonationProvider = CredentialsProvider({ checkUserIdentifier(creds); checkPermission(session); - // Get user who is being impersonated - const impersonatedUser = await prisma.user.findFirst({ - where: { - OR: [{ username: creds?.username }, { email: creds?.username }], - }, - select: { - id: true, - username: true, - role: true, - name: true, - email: true, - organizationId: true, - disableImpersonation: true, - locale: true, - teams: { - where: { - disableImpersonation: false, // Ensure they have impersonation enabled - accepted: true, // Ensure they are apart of the team and not just invited. - team: { - id: teamId, // Bring back only the right team - }, - }, - select: { - teamId: true, - disableImpersonation: true, - role: true, - }, - }, - }, - }); - - // Check if impersonating is allowed for this user - if (!impersonatedUser) { - throw new Error("This user does not exist"); - } + const impersonatedUser = await getImpersonatedUser({ session, teamId, creds }); if (session?.user.role === "ADMIN") { if (impersonatedUser.disableImpersonation) { diff --git a/packages/features/ee/teams/components/MemberListItem.tsx b/packages/features/ee/teams/components/MemberListItem.tsx index 1bfaa68b60..936732b7dc 100644 --- a/packages/features/ee/teams/components/MemberListItem.tsx +++ b/packages/features/ee/teams/components/MemberListItem.tsx @@ -321,7 +321,7 @@ export default function MemberListItem(props: Props) { onSubmit={async (e) => { e.preventDefault(); await signIn("impersonation-auth", { - username: props.member.username || props.member.email, + username: props.member.email, teamId: props.team.id, }); setShowImpersonateModal(false); diff --git a/packages/features/users/components/UserTable/ImpersonationMemberModal.tsx b/packages/features/users/components/UserTable/ImpersonationMemberModal.tsx index c5cf7b90d9..2753b6d635 100644 --- a/packages/features/users/components/UserTable/ImpersonationMemberModal.tsx +++ b/packages/features/users/components/UserTable/ImpersonationMemberModal.tsx @@ -27,7 +27,7 @@ export function ImpersonationMemberModal(props: { state: State; dispatch: Dispat onSubmit={async (e) => { e.preventDefault(); await signIn("impersonation-auth", { - username: user.username || user.email, + username: user.email, teamId: teamId, }); props.dispatch({ From 31f3d9778e2e9465e194007a7063cd9859c00b33 Mon Sep 17 00:00:00 2001 From: sean-brydon <55134778+sean-brydon@users.noreply.github.com> Date: Mon, 30 Oct 2023 15:29:09 +0000 Subject: [PATCH 2/2] Use correct typing for totalTeamMembers (#12152) --- .../availability/team/listTeamAvailability.handler.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/trpc/server/routers/viewer/availability/team/listTeamAvailability.handler.ts b/packages/trpc/server/routers/viewer/availability/team/listTeamAvailability.handler.ts index 96f20707a1..58c6132ba8 100644 --- a/packages/trpc/server/routers/viewer/availability/team/listTeamAvailability.handler.ts +++ b/packages/trpc/server/routers/viewer/availability/team/listTeamAvailability.handler.ts @@ -130,14 +130,15 @@ async function getInfoForAllTeams({ ctx, input }: GetOptions) { // Get total team count across all teams the user is in (for pagination) - const totalTeamMembers = - await prisma.$queryRaw`SELECT COUNT(DISTINCT "userId")::integer from "Membership" WHERE "teamId" IN (${Prisma.join( - teamIds - )})`; + const totalTeamMembers = await prisma.$queryRaw< + { + count: number; + }[] + >`SELECT COUNT(DISTINCT "userId")::integer from "Membership" WHERE "teamId" IN (${Prisma.join(teamIds)})`; return { teamMembers, - totalTeamMembers, + totalTeamMembers: totalTeamMembers[0].count, }; }