Files
calendar/apps/web/app/api/video/guest-session/route.ts
T
c196ff1095 feat: link email to participant (requireEmailForGuests) (#24661)
* feat: link email to participatn

* fix: bugs

* refactor: improve code

* refactor: prevent repload

* chore: remove unued

* fix: type

* refactor

* fix: type

* feat: restrict host

* feat: type

* feat: tests

* fix: don't allow guest

* fix: merk guest

* fix: bugs

* fix: test

* fix: test

* refactor: feedback

* fix: tests

---------

Co-authored-by: Volnei Munhoz <volnei.munhoz@gmail.com>
2025-11-05 11:15:41 +00:00

95 lines
3.4 KiB
TypeScript

import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { z } from "zod";
import {
generateGuestMeetingTokenFromOwnerMeetingToken,
updateMeetingTokenIfExpired,
} from "@calcom/app-store/dailyvideo/lib/VideoApiAdapter";
import { getHostsAndGuests } from "@calcom/features/bookings/lib/getHostsAndGuests";
import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository";
import { getCalVideoReference } from "@calcom/features/get-cal-video-reference";
import { VideoCallGuestRepository } from "@calcom/features/video-call-guest/repositories/VideoCallGuestRepository";
import prisma from "@calcom/prisma";
import { validateCsrfToken } from "@calcom/web/lib/validateCsrfToken";
const videoCallGuestWithCsrfSchema = z.object({
bookingUid: z.string(),
email: z.string().email(),
name: z.string().min(1),
csrfToken: z.string().length(64, "Invalid CSRF token"),
});
async function handler(req: NextRequest) {
let appDirRequestBody;
try {
appDirRequestBody = await req.json();
} catch {
return NextResponse.json({ success: false, message: "Invalid JSON" }, { status: 400 });
}
const guestData = videoCallGuestWithCsrfSchema.parse(appDirRequestBody);
const csrfError = await validateCsrfToken(guestData.csrfToken);
if (csrfError) {
return csrfError;
}
const bookingRepo = new BookingRepository(prisma);
const booking = await bookingRepo.findBookingIncludeCalVideoSettingsAndReferences({
bookingUid: guestData.bookingUid,
});
if (!booking) {
return NextResponse.json({ error: "Booking not found" }, { status: 404 });
}
const { hosts, guests } = getHostsAndGuests(booking);
const isHost = hosts.some((host) => host.email.toLowerCase() === guestData.email.trim().toLowerCase());
const isGuest = guests.some((guest) => guest.email.toLowerCase() === guestData.email.trim().toLowerCase());
if (isHost) {
return NextResponse.json({ error: "hosts_must_use_login" }, { status: 403 });
} else if (!isGuest) {
return NextResponse.json({ error: "invalid_guest_email" }, { status: 403 });
}
const videoCallGuestRepo = new VideoCallGuestRepository(prisma);
const guestSession = await videoCallGuestRepo.upsertVideoCallGuest({
bookingUid: guestData.bookingUid,
email: guestData.email,
name: guestData.name,
});
const videoReference = getCalVideoReference(booking.references);
if (!videoReference) {
return NextResponse.json({ error: "Video reference not found" }, { status: 404 });
}
const endTime = new Date(booking.endTime);
const fourteenDaysAfter = new Date(endTime.getTime() + 14 * 24 * 60 * 60 * 1000);
const epochTimeFourteenDaysAfter = Math.floor(fourteenDaysAfter.getTime() / 1000);
const videoReferencePassword = await updateMeetingTokenIfExpired({
bookingReferenceId: videoReference.id,
roomName: videoReference.uid,
meetingToken: videoReference.meetingPassword,
exp: epochTimeFourteenDaysAfter,
});
const guestMeetingPassword = await generateGuestMeetingTokenFromOwnerMeetingToken({
meetingToken: videoReferencePassword,
userId: guestSession.id,
});
return NextResponse.json({
guestSessionId: guestSession.id,
meetingPassword: guestMeetingPassword,
meetingUrl: videoReference.meetingUrl,
});
}
export const POST = defaultResponderForAppDir(handler);