fix: allow booking rejection via GET for email clients that do not support forms (#28029)

* fix: allow booking rejection via GET for email clients that do not support forms

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* Refactor test cases for GET handler in route.test.ts

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Anik Dhabal Babu
2026-02-18 17:23:42 +05:30
committed by GitHub
co-authored by anik@cal.com Devin AI
parent a78a3ffe39
commit 8908a66c23
2 changed files with 31 additions and 18 deletions
@@ -179,7 +179,22 @@ describe("verify-booking-token route", () => {
});
describe("GET handler", () => {
it("should redirect to booking page with error when action is reject (requires POST)", async () => {
it("should process rejection via GET (for email clients that don't support POST)", async () => {
createMockBooking({
id: 1,
uid: "abc123",
oneTimePassword: "test-token",
recurringEventId: null,
});
createMockUser({
id: 42,
uuid: "user-uuid",
email: "test@example.com",
username: "testuser",
role: "USER",
destinationCalendar: null,
});
const baseUrl =
"https://app.example.com/api/verify-booking-token?action=reject&token=test-token&bookingUid=abc123&userId=42";
const req = createMockRequest(baseUrl, "GET");
@@ -191,7 +206,16 @@ describe("verify-booking-token route", () => {
expect(redirectUrl.origin).toBe("https://app.example.com");
expect(redirectUrl.pathname).toBe("/booking/abc123");
expect(redirectUrl.searchParams.get("error")).toBe("Rejection requires POST method");
expect(redirectUrl.searchParams.get("error")).toBeNull();
expect(mockConfirmHandler).toHaveBeenCalledWith(
expect.objectContaining({
input: expect.objectContaining({
bookingId: 1,
confirmed: false,
actionSource: "MAGIC_LINK",
}),
})
);
});
it("should redirect with error when query params are invalid (missing action)", async () => {
+5 -16
View File
@@ -1,15 +1,14 @@
import { makeUserActor } from "@calcom/features/booking-audit/lib/makeActor";
import { distributedTracing } from "@calcom/lib/tracing/factory";
import prisma from "@calcom/prisma";
import { confirmHandler } from "@calcom/trpc/server/routers/viewer/bookings/confirm.handler";
import { TRPCError } from "@trpc/server";
import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir";
import { parseRequestData } from "app/api/parseRequestData";
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { z } from "zod";
import { distributedTracing } from "@calcom/lib/tracing/factory";
import prisma from "@calcom/prisma";
import { confirmHandler } from "@calcom/trpc/server/routers/viewer/bookings/confirm.handler";
import { TRPCError } from "@trpc/server";
import { makeUserActor } from "@calcom/features/booking-audit/lib/makeActor";
enum DirectAction {
ACCEPT = "accept",
REJECT = "reject",
@@ -28,16 +27,6 @@ async function getHandler(request: NextRequest) {
try {
const { action, token, bookingUid, userId } = querySchema.parse(queryParams);
if (action === DirectAction.REJECT) {
// Rejections should use POST method
return NextResponse.redirect(
new URL(
`/booking/${bookingUid}?error=${encodeURIComponent("Rejection requires POST method")}`,
request.url
)
);
}
return await handleBookingAction(action, token, bookingUid, userId, request, undefined);
} catch {
const bookingUid = queryParams.bookingUid || "";