diff --git a/apps/web/components/booking/BookingListItem.tsx b/apps/web/components/booking/BookingListItem.tsx
index 0d72d910c7..5d6bb9272c 100644
--- a/apps/web/components/booking/BookingListItem.tsx
+++ b/apps/web/components/booking/BookingListItem.tsx
@@ -598,9 +598,7 @@ function BookingListItem(booking: BookingItemProps) {
{isUpcoming && !isCancelled ? (
<>
- {isPending && (userId === booking.user?.id || booking.isUserTeamAdminOrOwner) && (
-
- )}
+ {isPending && }
{isConfirmed && }
{isRejected && {t("rejected")} }
>
diff --git a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts
index 1bd0203b64..a34f1946cf 100644
--- a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts
+++ b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts
@@ -17,7 +17,12 @@ import { getTranslation } from "@calcom/lib/server";
import { getUsersCredentials } from "@calcom/lib/server/getUsersCredentials";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import { prisma } from "@calcom/prisma";
-import { BookingStatus, MembershipRole, SchedulingType, WebhookTriggerEvents } from "@calcom/prisma/enums";
+import {
+ BookingStatus,
+ MembershipRole,
+ WebhookTriggerEvents,
+ UserPermissionRole,
+} from "@calcom/prisma/enums";
import type { EventTypeMetadata } from "@calcom/prisma/zod-utils";
import type { CalendarEvent } from "@calcom/types/Calendar";
import type { IAbstractPaymentService, PaymentApp } from "@calcom/types/PaymentService";
@@ -77,7 +82,6 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => {
id: true,
name: true,
parentId: true,
- members: true,
},
},
workflows: {
@@ -109,30 +113,13 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => {
},
});
- if (booking.userId !== user.id && booking.eventTypeId) {
- // Only query database when it is explicitly required.
- const eventType = await prisma.eventType.findFirst({
- where: {
- id: booking.eventTypeId,
- schedulingType: SchedulingType.COLLECTIVE,
- },
- select: {
- users: {
- select: {
- id: true,
- },
- },
- },
- });
-
- const membership = booking.eventType?.team?.members.find((membership) => membership.userId === user.id);
- const isTeamAdminOrOwner =
- membership?.role === MembershipRole.OWNER || membership?.role === MembershipRole.ADMIN;
-
- if (eventType && !eventType.users.find((user) => booking.userId === user.id) && !isTeamAdminOrOwner) {
- throw new TRPCError({ code: "UNAUTHORIZED", message: "UNAUTHORIZED" });
- }
- }
+ await checkIfUserIsAuthorizedToConfirmBooking({
+ eventTypeId: booking.eventTypeId,
+ loggedInUserId: user.id,
+ teamId: booking.eventType?.teamId,
+ bookingUserId: booking.userId,
+ userRole: user.role,
+ });
// Do not move this before authorization check.
// This is done to avoid exposing extra information to the requester.
@@ -425,3 +412,61 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => {
return { message, status };
};
+
+const checkIfUserIsAuthorizedToConfirmBooking = async ({
+ eventTypeId,
+ loggedInUserId,
+ teamId,
+ bookingUserId,
+ userRole,
+}: {
+ eventTypeId: number | null;
+ loggedInUserId: number;
+ teamId?: number | null;
+ bookingUserId: number | null;
+ userRole: string;
+}): Promise => {
+ // check system wide admin
+ if (userRole === UserPermissionRole.ADMIN) return;
+
+ // Check if the user is the owner of the event type
+ if (bookingUserId === loggedInUserId) return;
+
+ // Check if user is associated with the event type
+ if (eventTypeId) {
+ const [loggedInUserAsHostOfEventType, loggedInUserAsUserOfEventType] = await Promise.all([
+ prisma.eventType.findUnique({
+ where: {
+ id: eventTypeId,
+ hosts: { some: { userId: loggedInUserId } },
+ },
+ select: { id: true },
+ }),
+ prisma.eventType.findUnique({
+ where: {
+ id: eventTypeId,
+ users: { some: { id: loggedInUserId } },
+ },
+ select: { id: true },
+ }),
+ ]);
+
+ if (loggedInUserAsHostOfEventType || loggedInUserAsUserOfEventType) return;
+ }
+
+ // Check if the user is an admin/owner of the team the booking belongs to
+ if (teamId) {
+ const membership = await prisma.membership.findFirst({
+ where: {
+ userId: loggedInUserId,
+ teamId: teamId,
+ role: {
+ in: [MembershipRole.OWNER, MembershipRole.ADMIN],
+ },
+ },
+ });
+ if (membership) return;
+ }
+
+ throw new TRPCError({ code: "UNAUTHORIZED", message: "User is not authorized to confirm this booking" });
+};
diff --git a/packages/trpc/server/routers/viewer/bookings/get.handler.ts b/packages/trpc/server/routers/viewer/bookings/get.handler.ts
index a0cae887fc..ac00188940 100644
--- a/packages/trpc/server/routers/viewer/bookings/get.handler.ts
+++ b/packages/trpc/server/routers/viewer/bookings/get.handler.ts
@@ -3,7 +3,7 @@ import getAllUserBookings from "@calcom/lib/bookings/getAllUserBookings";
import type { PrismaClient } from "@calcom/prisma";
import { bookingMinimalSelect } from "@calcom/prisma";
import type { Prisma } from "@calcom/prisma/client";
-import { MembershipRole, type BookingStatus } from "@calcom/prisma/enums";
+import { type BookingStatus } from "@calcom/prisma/enums";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import type { TrpcSessionUser } from "../../../trpc";
@@ -229,7 +229,6 @@ export async function getBookings({
select: {
id: true,
name: true,
- members: true,
},
},
},
@@ -427,41 +426,39 @@ export async function getBookings({
// Now enrich bookings with relation data. We could have queried the relation data along with the bookings, but that would cause unnecessary queries to the database.
// Because Prisma is also going to query the select relation data sequentially, we are fine querying it separately here as it would be just 1 query instead of 4
- const bookings = (
- await prisma.booking.findMany({
- where: {
- id: {
- in: plainBookings.map((booking) => booking.id),
+
+ const bookings = await Promise.all(
+ (
+ await prisma.booking.findMany({
+ where: {
+ id: {
+ in: plainBookings.map((booking) => booking.id),
+ },
},
- },
- select: bookingSelect,
- // We need to get the sorted bookings here as well because plainBookings array is not correctly sorted
- orderBy,
+ select: bookingSelect,
+ // We need to get the sorted bookings here as well because plainBookings array is not correctly sorted
+ orderBy,
+ })
+ ).map(async (booking) => {
+ // If seats are enabled and the event is not set to show attendees, filter out attendees that are not the current user
+ if (booking.seatsReferences.length && !booking.eventType?.seatsShowAttendees) {
+ booking.attendees = booking.attendees.filter((attendee) => attendee.email === user.email);
+ }
+
+ return {
+ ...booking,
+ eventType: {
+ ...booking.eventType,
+ recurringEvent: parseRecurringEvent(booking.eventType?.recurringEvent),
+ eventTypeColor: parseEventTypeColor(booking.eventType?.eventTypeColor),
+ price: booking.eventType?.price || 0,
+ currency: booking.eventType?.currency || "usd",
+ metadata: EventTypeMetaDataSchema.parse(booking.eventType?.metadata || {}),
+ },
+ startTime: booking.startTime.toISOString(),
+ endTime: booking.endTime.toISOString(),
+ };
})
- ).map((booking) => {
- // If seats are enabled and the event is not set to show attendees, filter out attendees that are not the current user
- if (booking.seatsReferences.length && !booking.eventType?.seatsShowAttendees) {
- booking.attendees = booking.attendees.filter((attendee) => attendee.email === user.email);
- }
-
- const membership = booking.eventType?.team?.members.find((membership) => membership.userId === user.id);
- const isUserTeamAdminOrOwner =
- membership?.role === MembershipRole.OWNER || membership?.role === MembershipRole.ADMIN;
-
- return {
- ...booking,
- eventType: {
- ...booking.eventType,
- recurringEvent: parseRecurringEvent(booking.eventType?.recurringEvent),
- eventTypeColor: parseEventTypeColor(booking.eventType?.eventTypeColor),
- price: booking.eventType?.price || 0,
- currency: booking.eventType?.currency || "usd",
- metadata: EventTypeMetaDataSchema.parse(booking.eventType?.metadata || {}),
- },
- startTime: booking.startTime.toISOString(),
- endTime: booking.endTime.toISOString(),
- isUserTeamAdminOrOwner,
- };
- });
+ );
return { bookings, recurringInfo };
}
|