feat: skip authentication check for reschedule bookings with validation (#24903)

* feat: skip authentication check for reschedule bookings with validation

- Skip checkBookingRequiresAuthentication when rescheduleUid is present
- Add validation to ensure rescheduleUid points to a real booking
- Verify booking status is ACCEPTED (upcoming)
- Verify booking uses the same event-type
- Throw appropriate errors for invalid reschedule attempts

Co-Authored-By: morgan@cal.com <morgan@cal.com>

* refactor: move reschedule validation logic into checkBookingRequiresAuthentication

- Refactor checkBookingRequiresAuthentication to accept optional rescheduleUid parameter
- Move reschedule booking validation logic inside the method
- Simplify createBooking method by removing duplicate validation code
- Maintain same validation logic: check booking exists, is ACCEPTED, and uses same event-type

Co-Authored-By: morgan@cal.com <morgan@cal.com>

* fix: allow PENDING bookings to be rescheduled

- Update status validation to allow both ACCEPTED and PENDING bookings
- Change error message to reflect both allowed statuses
- PENDING bookings can now be rescheduled without authentication check

Co-Authored-By: morgan@cal.com <morgan@cal.com>

* refactor: separate reschedule validation from auth check

- Extract validateRescheduleBooking method to handle reschedule-specific validation
- Keep checkBookingRequiresAuthentication strictly for auth checks
- Use conditional logic in createBooking: validate reschedule OR check auth
- Improves code clarity by separating concerns per lauris's feedback

Co-Authored-By: morgan@cal.com <morgan@cal.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Morgan
2025-11-05 11:07:43 +00:00
committed by GitHub
co-authored by morgan@cal.com Devin AI
parent eb6418c0d5
commit bf0fbe4888
@@ -205,7 +205,11 @@ export class BookingsController_2024_04_15 {
clientId?.toString() || (await this.getOAuthClientIdFromEventType(body.eventTypeId));
const { orgSlug, locationUrl } = body;
try {
await this.checkBookingRequiresAuthentication(req, body.eventTypeId);
if (body.rescheduleUid) {
await this.validateRescheduleBooking(body.rescheduleUid, body.eventTypeId);
} else {
await this.checkBookingRequiresAuthentication(req, body.eventTypeId);
}
const bookingRequest = await this.createNextApiBookingRequest(req, oAuthClientId, locationUrl, isEmbed);
const booking = await this.regularBookingService.createBooking({
bookingData: bookingRequest.body,
@@ -459,6 +463,25 @@ export class BookingsController_2024_04_15 {
return oAuthClientParams.platformClientId;
}
private async validateRescheduleBooking(rescheduleUid: string, eventTypeId: number): Promise<void> {
const { bookingInfo } = await getBookingInfo(rescheduleUid);
if (!bookingInfo) {
throw new NotFoundException(
`Booking with UID=${rescheduleUid} does not exist. Cannot reschedule a non-existent booking.`
);
}
if (bookingInfo.status !== "ACCEPTED" && bookingInfo.status !== "PENDING") {
throw new BadRequestException(
`Booking with UID=${rescheduleUid} has invalid status (status: ${bookingInfo.status}). Only ACCEPTED or PENDING bookings can be rescheduled.`
);
}
if (bookingInfo.eventTypeId !== eventTypeId) {
throw new BadRequestException(
`Booking with UID=${rescheduleUid} is for a different event type (eventTypeId: ${bookingInfo.eventTypeId}). Cannot reschedule to a different event type (eventTypeId: ${eventTypeId}).`
);
}
}
private async checkBookingRequiresAuthentication(req: Request, eventTypeId: number): Promise<void> {
const eventType = await this.eventTypeRepository.findByIdIncludeHostsAndTeamMembers({
id: eventTypeId,