diff --git a/apps/api/v2/src/ee/bookings/2024-04-15/controllers/bookings.controller.ts b/apps/api/v2/src/ee/bookings/2024-04-15/controllers/bookings.controller.ts index ebad60d604075e..5b6223c58a4d05 100644 --- a/apps/api/v2/src/ee/bookings/2024-04-15/controllers/bookings.controller.ts +++ b/apps/api/v2/src/ee/bookings/2024-04-15/controllers/bookings.controller.ts @@ -125,7 +125,7 @@ export class BookingsController_2024_04_15 { private readonly instantBookingCreateService: InstantBookingCreateService, private readonly eventTypeRepository: PrismaEventTypeRepository, private readonly teamRepository: PrismaTeamRepository - ) {} + ) { } @Get("/") @UseGuards(ApiAuthGuard) @@ -270,6 +270,7 @@ export class BookingsController_2024_04_15 { const res = await handleCancelBooking({ bookingData: bookingRequest.body, userId: bookingRequest.userId, + userUuid: bookingRequest.userUuid, arePlatformEmailsEnabled: bookingRequest.arePlatformEmailsEnabled, platformClientId: bookingRequest.platformClientId, platformCancelUrl: bookingRequest.platformCancelUrl, @@ -300,7 +301,7 @@ export class BookingsController_2024_04_15 { @Permissions([BOOKING_WRITE]) @UseGuards(ApiAuthGuard) async markNoShow( - @GetUser("id") userId: number, + @GetUser() user: UserWithProfile, @Body() body: MarkNoShowInput_2024_04_15, @Param("bookingUid") bookingUid: string ): Promise { @@ -309,7 +310,8 @@ export class BookingsController_2024_04_15 { bookingUid: bookingUid, attendees: body.attendees, noShowHost: body.noShowHost, - userId, + userId: user.id, + userUuid: user.uuid, }); return { status: SUCCESS_STATUS, data: markNoShowResponse }; @@ -378,7 +380,7 @@ export class BookingsController_2024_04_15 { ): Promise> { const oAuthClientId = clientId?.toString() || (await this.getOAuthClientIdFromEventType(body.eventTypeId)); - req.userId = (await this.getOwnerId(req)) ?? -1; + req.userId = (await this.getOwner(req))?.id ?? -1; try { const bookingReq = await this.createNextApiBookingRequest(req, oAuthClientId, undefined, isEmbed); const instantMeeting = await this.instantBookingCreateService.createBooking({ @@ -405,30 +407,45 @@ export class BookingsController_2024_04_15 { throw new InternalServerErrorException("Could not create instant booking."); } - private async getOwnerId(req: Request): Promise { + private async getOwner(req: Request): Promise<{ id: number; uuid: string } | null> { try { const bearerToken = req.get("Authorization")?.replace("Bearer ", ""); - if (bearerToken) { - if (isApiKey(bearerToken, this.config.get("api.apiKeyPrefix") ?? "cal_")) { - const strippedApiKey = stripApiKey(bearerToken, this.config.get("api.keyPrefix")); - const apiKeyHash = sha256Hash(strippedApiKey); - const keyData = await this.apiKeyRepository.getApiKeyFromHash(apiKeyHash); - return keyData?.userId; - } else { - // Access Token - const ownerId = await this.oAuthFlowService.getOwnerId(bearerToken); - return ownerId; - } + if (!bearerToken) { + return null; + } + + let ownerId: number | null = null; + + if (isApiKey(bearerToken, this.config.get("api.apiKeyPrefix") ?? "cal_")) { + const strippedApiKey = stripApiKey(bearerToken, this.config.get("api.keyPrefix")); + const apiKeyHash = sha256Hash(strippedApiKey); + const keyData = await this.apiKeyRepository.getApiKeyFromHash(apiKeyHash); + ownerId = keyData?.userId ?? null; + } else { + // Access Token + ownerId = await this.oAuthFlowService.getOwnerId(bearerToken); } + + if (!ownerId) { + return null; + } + + const user = await this.usersRepository.findById(ownerId); + if (!user) { + return null; + } + + return { id: user.id, uuid: user.uuid }; } catch (err) { this.logger.error(err); + return null; } } - private async getOwnerIdRescheduledBooking( + private async getOwnerRescheduledBooking( request: Request, platformClientId?: string - ): Promise { + ): Promise<{ id: number; uuid: string } | null> { if ( platformClientId && request.body.rescheduledBy && @@ -440,13 +457,15 @@ export class BookingsController_2024_04_15 { ); } - if (request.body.rescheduledBy) { - if (request.body.rescheduledBy !== request.body.responses.email) { - return (await this.usersRepository.findByEmail(request.body.rescheduledBy))?.id; + if (request.body.rescheduledBy && request.body.rescheduledBy !== request.body.responses.email) { + const user = await this.usersRepository.findByEmail(request.body.rescheduledBy); + if (!user) { + return null; } + return { id: user.id, uuid: user.uuid }; } - return undefined; + return null; } private async getOAuthClientIdFromEventType(eventTypeId: number): Promise { @@ -498,7 +517,8 @@ export class BookingsController_2024_04_15 { } } - const userId = await this.getOwnerId(req); + const owner = await this.getOwner(req); + const userId = owner?.id; if (!userId) { throw new UnauthorizedException( @@ -561,12 +581,16 @@ export class BookingsController_2024_04_15 { oAuthClientId?: string, platformBookingLocation?: string, isEmbed?: string - ): Promise { + ): Promise { const requestId = req.get("X-Request-Id"); const clone = { ...req }; - const userId = clone.body.rescheduleUid - ? await this.getOwnerIdRescheduledBooking(req, oAuthClientId) - : await this.getOwnerId(req); + const owner = clone.body.rescheduleUid + ? await this.getOwnerRescheduledBooking(req, oAuthClientId) + : await this.getOwner(req); + + const userId = owner?.id; + const userUuid = owner?.uuid; + const oAuthParams = oAuthClientId ? await this.getOAuthClientsParams(oAuthClientId, this.transformToBoolean(isEmbed)) : DEFAULT_PLATFORM_PARAMS; @@ -577,7 +601,7 @@ export class BookingsController_2024_04_15 { oAuthClientId, ...oAuthParams, }); - Object.assign(clone, { userId, ...oAuthParams, platformBookingLocation }); + Object.assign(clone, { userId, userUuid, ...oAuthParams, platformBookingLocation }); clone.body = { ...clone.body, noEmail: !oAuthParams.arePlatformEmailsEnabled, @@ -586,7 +610,7 @@ export class BookingsController_2024_04_15 { if (oAuthClientId) { await this.setPlatformAttendeesEmails(clone.body, oAuthClientId); } - return clone as unknown as NextApiRequest & { userId?: number } & OAuthRequestParams; + return clone as unknown as NextApiRequest & { userId?: number; userUuid?: string } & OAuthRequestParams; } async setPlatformAttendeesEmails( @@ -612,9 +636,12 @@ export class BookingsController_2024_04_15 { oAuthClientId?: string, platformBookingLocation?: string, isEmbed?: string - ): Promise { + ): Promise { const clone = { ...req }; - const userId = (await this.getOwnerId(req)) ?? -1; + const owner = await this.getOwner(req); + const userId = owner?.id ?? -1; + const userUuid = owner?.uuid; + const oAuthParams = oAuthClientId ? await this.getOAuthClientsParams(oAuthClientId, this.transformToBoolean(isEmbed)) : DEFAULT_PLATFORM_PARAMS; @@ -628,6 +655,7 @@ export class BookingsController_2024_04_15 { }); Object.assign(clone, { userId, + userUuid, ...oAuthParams, platformBookingLocation, noEmail: !oAuthParams.arePlatformEmailsEnabled, @@ -636,7 +664,7 @@ export class BookingsController_2024_04_15 { if (oAuthClientId) { await this.setPlatformAttendeesEmails(clone.body, oAuthClientId); } - return clone as unknown as NextApiRequest & { userId?: number } & OAuthRequestParams; + return clone as unknown as NextApiRequest & { userId?: number; userUuid?: string } & OAuthRequestParams; } private handleBookingErrors( diff --git a/apps/api/v2/src/ee/bookings/2024-08-13/controllers/bookings.controller.ts b/apps/api/v2/src/ee/bookings/2024-08-13/controllers/bookings.controller.ts index 166cf8445bb369..586adbb5009b00 100644 --- a/apps/api/v2/src/ee/bookings/2024-08-13/controllers/bookings.controller.ts +++ b/apps/api/v2/src/ee/bookings/2024-08-13/controllers/bookings.controller.ts @@ -397,18 +397,18 @@ export class BookingsController_2024_08_13 { Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint. `, }) - async markNoShow( - @Param("bookingUid") bookingUid: string, - @Body() body: MarkAbsentBookingInput_2024_08_13, - @GetUser("id") ownerId: number - ): Promise { - const booking = await this.bookingsService.markAbsent(bookingUid, ownerId, body); - - return { - status: SUCCESS_STATUS, - data: booking, - }; - } + async markNoShow( + @Param("bookingUid") bookingUid: string, + @Body() body: MarkAbsentBookingInput_2024_08_13, + @GetUser() user: ApiAuthGuardUser + ): Promise { + const booking = await this.bookingsService.markAbsent(bookingUid, user.id, body, user.uuid); + + return { + status: SUCCESS_STATUS, + data: booking, + }; + } @Post("/:bookingUid/reassign") @HttpCode(HttpStatus.OK) diff --git a/apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts b/apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts index bca52edae34964..4e54f316e1f1a5 100644 --- a/apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts +++ b/apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts @@ -919,34 +919,40 @@ export class BookingsService_2024_08_13 { return await this.getBooking(recurringBookingUid, authUser); } - async markAbsent(bookingUid: string, bookingOwnerId: number, body: MarkAbsentBookingInput_2024_08_13) { - const bodyTransformed = this.inputService.transformInputMarkAbsentBooking(body); - const bookingBefore = await this.bookingsRepository.getByUid(bookingUid); - - if (!bookingBefore) { - throw new NotFoundException(`Booking with uid=${bookingUid} not found.`); - } + async markAbsent( + bookingUid: string, + bookingOwnerId: number, + body: MarkAbsentBookingInput_2024_08_13, + userUuid?: string + ) { + const bodyTransformed = this.inputService.transformInputMarkAbsentBooking(body); + const bookingBefore = await this.bookingsRepository.getByUid(bookingUid); + + if (!bookingBefore) { + throw new NotFoundException(`Booking with uid=${bookingUid} not found.`); + } - const nowUtc = DateTime.utc(); - const bookingStartTimeUtc = DateTime.fromJSDate(bookingBefore.startTime, { zone: "utc" }); + const nowUtc = DateTime.utc(); + const bookingStartTimeUtc = DateTime.fromJSDate(bookingBefore.startTime, { zone: "utc" }); - if (nowUtc < bookingStartTimeUtc) { - throw new BadRequestException( - `Bookings can only be marked as absent after their scheduled start time. Current time in UTC+0: ${nowUtc.toISO()}, Booking start time in UTC+0: ${bookingStartTimeUtc.toISO()}` - ); - } + if (nowUtc < bookingStartTimeUtc) { + throw new BadRequestException( + `Bookings can only be marked as absent after their scheduled start time. Current time in UTC+0: ${nowUtc.toISO()}, Booking start time in UTC+0: ${bookingStartTimeUtc.toISO()}` + ); + } - const platformClientParams = bookingBefore?.eventTypeId - ? await this.platformBookingsService.getOAuthClientParams(bookingBefore.eventTypeId) - : undefined; + const platformClientParams = bookingBefore?.eventTypeId + ? await this.platformBookingsService.getOAuthClientParams(bookingBefore.eventTypeId) + : undefined; - await handleMarkNoShow({ - bookingUid, - attendees: bodyTransformed.attendees, - noShowHost: bodyTransformed.noShowHost, - userId: bookingOwnerId, - platformClientParams, - }); + await handleMarkNoShow({ + bookingUid, + attendees: bodyTransformed.attendees, + noShowHost: bodyTransformed.noShowHost, + userId: bookingOwnerId, + userUuid, + platformClientParams, + }); const booking = await this.bookingsRepository.getByUidWithAttendeesAndUserAndEvent(bookingUid); diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index 17275efe63b90f..46bc3136af9160 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -66,6 +66,7 @@ export type BookingToDelete = Awaited>; export type CancelBookingInput = { userId?: number; + userUuid?: string; bookingData: z.infer; } & PlatformParams; diff --git a/packages/features/handleMarkNoShow.ts b/packages/features/handleMarkNoShow.ts index 4ed61fe5c43c18..54b149eb3a26c6 100644 --- a/packages/features/handleMarkNoShow.ts +++ b/packages/features/handleMarkNoShow.ts @@ -45,7 +45,7 @@ const buildResultPayload = async ( }; }; -const logFailedResults = (results: PromiseSettledResult[]) => { +const logFailedResults = (results: PromiseSettledResult[]) => { const failed = results.filter((x) => x.status === "rejected") as PromiseRejectedResult[]; if (failed.length < 1) return; const failedMessage = failed.map((r) => r.reason); @@ -89,10 +89,12 @@ const handleMarkNoShow = async ({ attendees, noShowHost, userId, + userUuid: _userUuid, locale, platformClientParams, }: TNoShowInputSchema & { userId?: number; + userUuid?: string; locale?: string; platformClientParams?: PlatformClientParams; }) => { diff --git a/packages/features/users/repositories/UserRepository.ts b/packages/features/users/repositories/UserRepository.ts index fea93ffd5b9ef1..3e23c7862ddcc2 100644 --- a/packages/features/users/repositories/UserRepository.ts +++ b/packages/features/users/repositories/UserRepository.ts @@ -1014,18 +1014,19 @@ export class UserRepository { }; } - async findUnlockedUserForSession({ userId }: { userId: number }) { - const user = await this.prismaClient.user.findUnique({ - where: { - id: userId, - // Locked users can't login - locked: false, - }, - select: { - id: true, - username: true, - name: true, - email: true, + async findUnlockedUserForSession({ userId }: { userId: number }) { + const user = await this.prismaClient.user.findUnique({ + where: { + id: userId, + // Locked users can't login + locked: false, + }, + select: { + id: true, + uuid: true, + username: true, + name: true, + email: true, emailVerified: true, bio: true, avatarUrl: true, diff --git a/packages/trpc/server/middlewares/sessionMiddleware.ts b/packages/trpc/server/middlewares/sessionMiddleware.ts index ffddaa0198c85c..ab8937ba178763 100644 --- a/packages/trpc/server/middlewares/sessionMiddleware.ts +++ b/packages/trpc/server/middlewares/sessionMiddleware.ts @@ -45,10 +45,10 @@ export async function getUserFromSession(ctx: TRPCContextInner, session: Maybe>; diff --git a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts index dd1575352bfb27..c3d5916d1a4e94 100644 --- a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts @@ -40,7 +40,7 @@ import type { TConfirmInputSchema } from "./confirm.schema"; type ConfirmOptions = { ctx: { - user: Pick, "id" | "email" | "username" | "role" | "destinationCalendar">; + user: Pick, "id" | "uuid" | "email" | "username" | "role" | "destinationCalendar">; }; input: TConfirmInputSchema; };