fix: Org/team admin or owner should be marked as ‘no show’ in a member’s booking (#26009)

* fix: update tempOrgRediret on updating s
lug

* Update packages/trpc/server/routers/viewer/organizations/adminUpdate.handler.ts

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* refactor

* fix

* fix

* revert

* fix handle no show

* revert

* Remove metadata from organization query selection

Removed metadata selection from organization query.

* use repo

* update

* Update packages/features/bookings/lib/checkIfUserIsAuthorizedToManageBooking.ts

Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>

* update

---------

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
This commit is contained in:
Anik Dhabal Babu
2025-12-22 15:42:20 -03:00
committed by GitHub
co-authored by cubic-dev-ai[bot] Syed Ali Shahbaz
parent 33eee8dfc3
commit 23920fe9ce
3 changed files with 38 additions and 88 deletions
@@ -16,9 +16,9 @@ import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.rep
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
import { TokensRepositoryFixture } from "test/fixtures/repository/tokens.repository.fixture";
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
import { randomString } from "test/utils/randomString";
import { withApiAuth } from "test/utils/withApiAuth";
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
import {
@@ -73,6 +73,7 @@ type EmailSetup = {
eventTypeId: number;
createdBookingUid: string;
rescheduledBookingUid: string;
accessToken: string;
};
describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
@@ -85,6 +86,7 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
let oauthClientRepositoryFixture: OAuthClientRepositoryFixture;
let teamRepositoryFixture: TeamRepositoryFixture;
let tokensRepositoryFixture: TokensRepositoryFixture;
let emailsEnabledSetup: EmailSetup;
let emailsDisabledSetup: EmailSetup;
@@ -94,12 +96,9 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
const userEmailsDisabled = `confirm-emails-2024-08-13-user-${randomString()}@api.com`;
beforeAll(async () => {
const moduleRef = await withApiAuth(
authEmail,
Test.createTestingModule({
imports: [AppModule, PrismaModule, UsersModule, SchedulesModule_2024_04_15],
})
)
const moduleRef = await Test.createTestingModule({
imports: [AppModule, PrismaModule, UsersModule, SchedulesModule_2024_04_15],
})
.overrideGuard(PermissionsGuard)
.useValue({
canActivate: () => true,
@@ -112,6 +111,7 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
tokensRepositoryFixture = new TokensRepositoryFixture(moduleRef);
organization = await teamRepositoryFixture.create({
name: `confirm-emails-2024-08-13-organization-${randomString()}`,
@@ -152,6 +152,8 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
},
});
const tokens = await tokensRepositoryFixture.createTokens(user.id, oAuthClientEmailsEnabled.id);
const userSchedule: CreateScheduleInput_2024_04_15 = {
name: `confirm-emails-2024-08-13-schedule-${randomString()}`,
timeZone: "Europe/Rome",
@@ -174,6 +176,7 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
eventTypeId: event.id,
createdBookingUid: "",
rescheduledBookingUid: "",
accessToken: tokens.accessToken,
};
}
@@ -188,6 +191,8 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
},
},
});
const tokens = await tokensRepositoryFixture.createTokens(user.id, oAuthClientEmailsDisabled.id);
const userSchedule: CreateScheduleInput_2024_04_15 = {
name: `confirm-emails-2024-08-13-schedule-${randomString()}`,
timeZone: "Europe/Rome",
@@ -209,6 +214,7 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
eventTypeId: event.id,
createdBookingUid: "",
rescheduledBookingUid: "",
accessToken: tokens.accessToken,
};
}
@@ -277,6 +283,7 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
return request(app.getHttpServer())
.post(`/v2/bookings/${emailsDisabledSetup.createdBookingUid}/confirm`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.set("Authorization", `Bearer ${emailsDisabledSetup.accessToken}`)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingOutput_2024_08_13 = response.body;
@@ -331,6 +338,7 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
return request(app.getHttpServer())
.post(`/v2/bookings/${emailsDisabledSetup.createdBookingUid}/decline`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.set("Authorization", `Bearer ${emailsDisabledSetup.accessToken}`)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingOutput_2024_08_13 = response.body;
@@ -391,6 +399,7 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
return request(app.getHttpServer())
.post(`/v2/bookings/${emailsEnabledSetup.createdBookingUid}/confirm`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.set("Authorization", `Bearer ${emailsEnabledSetup.accessToken}`)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingOutput_2024_08_13 = response.body;
@@ -508,6 +517,7 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
return request(app.getHttpServer())
.post(`/v2/bookings/${emailsEnabledSetup.createdBookingUid}/decline`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.set("Authorization", `Bearer ${emailsEnabledSetup.accessToken}`)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingOutput_2024_08_13 = response.body;
+8 -2
View File
@@ -1,6 +1,7 @@
import { type TFunction } from "i18next";
import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository";
import { BookingAccessService } from "@calcom/features/bookings/services/BookingAccessService";
import { CreditService } from "@calcom/features/ee/billing/credit-service";
import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer";
import { workflowSelect } from "@calcom/features/ee/workflows/lib/getAllWorkflows";
@@ -423,9 +424,14 @@ const assertCanAccessBooking = async (bookingUid: string, userId?: number) => {
if (!userId) throw new HttpError({ statusCode: 401 });
const bookingRepo = new BookingRepository(prisma);
const booking = await bookingRepo.findBookingByUidAndUserId({ bookingUid, userId });
const booking = await bookingRepo.findByUidIncludeEventTypeAndReferences({ bookingUid });
const bookingAccessService = new BookingAccessService(prisma);
const isAuthorized = await bookingAccessService.doesUserIdHaveAccessToBooking({
userId,
bookingUid,
});
if (!booking)
if (!isAuthorized)
throw new HttpError({ statusCode: 403, message: "You are not allowed to access this booking" });
const isUpcoming = new Date(booking.endTime) >= new Date();
@@ -7,12 +7,12 @@ import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventR
import { handleConfirmation } from "@calcom/features/bookings/lib/handleConfirmation";
import { handleWebhookTrigger } from "@calcom/features/bookings/lib/handleWebhookTrigger";
import { processPaymentRefund } from "@calcom/features/bookings/lib/payment/processPaymentRefund";
import { BookingAccessService } from "@calcom/features/bookings/services/BookingAccessService";
import { CreditService } from "@calcom/features/ee/billing/credit-service";
import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer";
import { workflowSelect } from "@calcom/features/ee/workflows/lib/getAllWorkflows";
import { getAllWorkflowsFromEventType } from "@calcom/features/ee/workflows/lib/getAllWorkflowsFromEventType";
import { WorkflowService } from "@calcom/features/ee/workflows/lib/service/WorkflowService";
import { PrismaOrgMembershipRepository } from "@calcom/features/membership/repositories/PrismaOrgMembershipRepository";
import type { GetSubscriberOptions } from "@calcom/features/webhooks/lib/getWebhooks";
import type { EventPayloadType, EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload";
import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId";
@@ -24,13 +24,7 @@ import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import type { TraceContext } from "@calcom/lib/tracing";
import { prisma } from "@calcom/prisma";
import { Prisma } from "@calcom/prisma/client";
import {
BookingStatus,
MembershipRole,
WebhookTriggerEvents,
WorkflowTriggerEvents,
UserPermissionRole,
} from "@calcom/prisma/enums";
import { BookingStatus, WebhookTriggerEvents, WorkflowTriggerEvents } from "@calcom/prisma/enums";
import type { EventTypeMetadata } from "@calcom/prisma/zod-utils";
import type { CalendarEvent } from "@calcom/types/Calendar";
@@ -149,14 +143,19 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => {
throw new TRPCError({ code: "BAD_REQUEST", message: "Booking must have an organizer" });
}
await checkIfUserIsAuthorizedToConfirmBooking({
eventTypeId: booking.eventTypeId,
loggedInUserId: ctx.user.id,
teamId: booking.eventType?.teamId || booking.eventType?.parent?.teamId,
bookingUserId: booking.userId,
userRole: ctx.user.role,
const bookingAccessService = new BookingAccessService(prisma);
const isUserAuthorizedToConfirmBooking = await bookingAccessService.doesUserIdHaveAccessToBooking({
userId: ctx.user.id,
bookingId: bookingId,
});
if (!isUserAuthorizedToConfirmBooking) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "User is not authorized to confirm this booking",
});
}
// Do not move this before authorization check.
// This is done to avoid exposing extra information to the requester.
if (booking.status === BookingStatus.ACCEPTED) {
@@ -443,68 +442,3 @@ 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<void> => {
// 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;
}
if (
bookingUserId &&
(await PrismaOrgMembershipRepository.isLoggedInUserOrgAdminOfBookingHost(loggedInUserId, bookingUserId))
) {
return;
}
throw new TRPCError({ code: "UNAUTHORIZED", message: "User is not authorized to confirm this booking" });
};