diff --git a/apps/web/pages/[user]/calendar-cache/[month].tsx b/apps/web/pages/[user]/calendar-cache/[month].tsx index 8eff9ad2fc..9cd9359bb5 100644 --- a/apps/web/pages/[user]/calendar-cache/[month].tsx +++ b/apps/web/pages/[user]/calendar-cache/[month].tsx @@ -18,7 +18,7 @@ export const getStaticProps: GetStaticProps< { user: string } > = async (context) => { const { user: username, month } = paramsSchema.parse(context.params); - const user = await prisma.user.findUnique({ + const userWithCredentials = await prisma.user.findUnique({ where: { username, }, @@ -34,14 +34,15 @@ export const getStaticProps: GetStaticProps< ).startOf("day"); const endDate = startDate.endOf("month"); try { - const results = user?.credentials + const results = userWithCredentials?.credentials ? await getCachedResults( - user?.credentials, + userWithCredentials?.credentials, startDate.format(), endDate.format(), - user?.selectedCalendars + userWithCredentials?.selectedCalendars ) : []; + return { props: { results, date: new Date().toISOString() }, revalidate: 1, diff --git a/apps/web/pages/api/availability/calendar.ts b/apps/web/pages/api/availability/calendar.ts index aecbe4f51b..5726fb3827 100644 --- a/apps/web/pages/api/availability/calendar.ts +++ b/apps/web/pages/api/availability/calendar.ts @@ -14,7 +14,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) return; } - const user = await prisma.user.findUnique({ + const userWithCredentials = await prisma.user.findUnique({ where: { id: session.user.id, }, @@ -25,11 +25,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) selectedCalendars: true, }, }); - - if (!user) { + if (!userWithCredentials) { res.status(401).json({ message: "Not authenticated" }); return; } + const { credentials, ...user } = userWithCredentials; if (req.method === "POST") { await prisma.selectedCalendar.upsert({ @@ -80,7 +80,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) }); // get user's credentials + their connected integrations - const calendarCredentials = getCalendarCredentials(user.credentials); + const calendarCredentials = getCalendarCredentials(credentials); // get all the connected integrations' calendars (from third party) const { connectedCalendars } = await getConnectedCalendars(calendarCredentials, user.selectedCalendars); const calendars = connectedCalendars.flatMap((c) => c.calendars).filter(notEmpty); diff --git a/apps/web/pages/api/recorded-daily-video.ts b/apps/web/pages/api/recorded-daily-video.ts index 0ff65f7dc5..19de912751 100644 --- a/apps/web/pages/api/recorded-daily-video.ts +++ b/apps/web/pages/api/recorded-daily-video.ts @@ -54,7 +54,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { user: { select: { id: true, - credentials: true, timeZone: true, email: true, name: true, diff --git a/apps/web/pages/video/[uid].tsx b/apps/web/pages/video/[uid].tsx index 721de06a56..1e09536fe5 100644 --- a/apps/web/pages/video/[uid].tsx +++ b/apps/web/pages/video/[uid].tsx @@ -282,7 +282,6 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { user: { select: { id: true, - credentials: true, timeZone: true, name: true, email: true, diff --git a/packages/core/getUserAvailability.ts b/packages/core/getUserAvailability.ts index 7ba043dc02..16d94b5573 100644 --- a/packages/core/getUserAvailability.ts +++ b/packages/core/getUserAvailability.ts @@ -70,7 +70,10 @@ type EventType = Awaited>; const getUser = (where: Prisma.UserWhereUniqueInput) => prisma.user.findUnique({ where, - select: availabilityUserSelect, + select: { + ...availabilityUserSelect, + credentials: true, + }, }); type User = Awaited>; diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index 495bebe570..324ff32534 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -38,7 +38,7 @@ async function getBookingToDelete(id: number | undefined, uid: string | undefine user: { select: { id: true, - credentials: true, + credentials: true, // Not leaking at the moment, be careful with email: true, timeZone: true, name: true, diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index aecef71f4c..5b2c33c566 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -197,7 +197,12 @@ const getEventTypesFromDB = async (eventTypeId: number) => { id: true, customInputs: true, disableGuests: true, - users: userSelect, + users: { + select: { + credentials: true, + ...userSelect.select, + }, + }, slug: true, team: { select: { @@ -253,7 +258,12 @@ const getEventTypesFromDB = async (eventTypeId: number) => { hosts: { select: { isFixed: true, - user: userSelect, + user: { + select: { + credentials: true, + ...userSelect.select, + }, + }, }, }, availability: { @@ -277,7 +287,7 @@ const getEventTypesFromDB = async (eventTypeId: number) => { }; }; -type IsFixedAwareUser = User & { isFixed: boolean }; +type IsFixedAwareUser = User & { isFixed: boolean; credentials: Credential[] }; async function ensureAvailableUsers( eventType: Awaited> & { @@ -623,6 +633,7 @@ async function handler( }, select: { ...userSelect.select, + credentials: true, // Don't leak to client metadata: true, }, }) @@ -654,7 +665,10 @@ async function handler( where: { id: eventType.userId, }, - ...userSelect, + select: { + credentials: true, // Don't leak to client + ...userSelect.select, + }, }); if (!eventTypeUser) { log.warn({ message: "NewBooking: eventTypeUser.notFound" }); diff --git a/packages/features/ee/payments/api/webhook.ts b/packages/features/ee/payments/api/webhook.ts index dc3e97d5db..797cf1b6b9 100644 --- a/packages/features/ee/payments/api/webhook.ts +++ b/packages/features/ee/payments/api/webhook.ts @@ -55,7 +55,6 @@ async function getBooking(bookingId: number) { select: { id: true, username: true, - credentials: true, timeZone: true, email: true, name: true, @@ -176,9 +175,11 @@ async function handlePaymentSuccess(event: Stripe.Event) { eventTypeRaw = await getEventType(booking.eventTypeId); } - const { user } = booking; + const { user: userWithCredentials } = booking; - if (!user) throw new HttpCode({ statusCode: 204, message: "No user found" }); + if (!userWithCredentials) throw new HttpCode({ statusCode: 204, message: "No user found" }); + + const { credentials, ...user } = userWithCredentials; const t = await getTranslation(user.locale ?? "en", "common"); const attendeesListPromises = booking.attendees.map(async (attendee) => { @@ -227,7 +228,7 @@ async function handlePaymentSuccess(event: Stripe.Event) { const isConfirmed = booking.status === BookingStatus.ACCEPTED; if (isConfirmed) { - const eventManager = new EventManager(user); + const eventManager = new EventManager(userWithCredentials); const scheduleResult = await eventManager.create(evt); bookingData.references = { create: scheduleResult.referencesToCreate }; } @@ -255,7 +256,14 @@ async function handlePaymentSuccess(event: Stripe.Event) { await prisma.$transaction([paymentUpdate, bookingUpdate]); if (!isConfirmed && !eventTypeRaw?.requiresConfirmation) { - await handleConfirmation({ user, evt, prisma, bookingId: booking.id, booking, paid: true }); + await handleConfirmation({ + user: userWithCredentials, + evt, + prisma, + bookingId: booking.id, + booking, + paid: true, + }); } else { await sendScheduledEmails({ ...evt }); } @@ -276,14 +284,32 @@ const handleSetupSuccess = async (event: Stripe.Event) => { if (!payment?.data || !payment?.id) throw new HttpCode({ statusCode: 204, message: "Payment not found" }); - const { booking, user, evt, eventTypeRaw } = await getBooking(payment.bookingId); + const { user, evt, eventTypeRaw } = await getBooking(payment.bookingId); const bookingData: Prisma.BookingUpdateInput = { paid: true, }; + const userWithCredentials = await prisma.user.findUnique({ + where: { + id: user.id, + }, + select: { + id: true, + username: true, + timeZone: true, + email: true, + name: true, + locale: true, + destinationCalendar: true, + credentials: true, + }, + }); + + if (!userWithCredentials) throw new HttpCode({ statusCode: 204, message: "No user found" }); + if (!eventTypeRaw?.requiresConfirmation) { - const eventManager = new EventManager(user); + const eventManager = new EventManager(userWithCredentials); const scheduleResult = await eventManager.create(evt); bookingData.references = { create: scheduleResult.referencesToCreate }; bookingData.status = BookingStatus.ACCEPTED; diff --git a/packages/lib/defaultEvents.ts b/packages/lib/defaultEvents.ts index e76525b779..36f1671e4d 100644 --- a/packages/lib/defaultEvents.ts +++ b/packages/lib/defaultEvents.ts @@ -1,4 +1,4 @@ -import type { Prisma } from "@prisma/client"; +import type { Prisma, Credential } from "@prisma/client"; import { PeriodType, SchedulingType } from "@prisma/client"; import { DailyLocationType } from "@calcom/app-store/locations"; @@ -25,7 +25,7 @@ type UsernameSlugLinkProps = { slug: string; }; -const user: User = { +const user: User & { credentials: Credential[] } = { metadata: null, theme: null, credentials: [], diff --git a/packages/prisma/selects/user.ts b/packages/prisma/selects/user.ts index e87f6a511d..335c7e476b 100644 --- a/packages/prisma/selects/user.ts +++ b/packages/prisma/selects/user.ts @@ -1,16 +1,15 @@ import { Prisma } from "@prisma/client"; export const availabilityUserSelect = Prisma.validator()({ - credentials: true, + id: true, timeZone: true, bufferTime: true, - availability: true, - id: true, startTime: true, username: true, endTime: true, - selectedCalendars: true, timeFormat: true, + defaultScheduleId: true, + // Relationships schedules: { select: { availability: true, @@ -18,7 +17,8 @@ export const availabilityUserSelect = Prisma.validator()({ id: true, }, }, - defaultScheduleId: true, + availability: true, + selectedCalendars: true, }); export const baseUserSelect = Prisma.validator()({ diff --git a/packages/trpc/server/routers/viewer.tsx b/packages/trpc/server/routers/viewer.tsx index 290ef1728d..b2468cc89a 100644 --- a/packages/trpc/server/routers/viewer.tsx +++ b/packages/trpc/server/routers/viewer.tsx @@ -1041,7 +1041,6 @@ const loggedInViewerRouter = router({ user: { select: { id: true, - credentials: true, email: true, timeZone: true, name: true, diff --git a/packages/trpc/server/routers/viewer/slots.ts b/packages/trpc/server/routers/viewer/slots.ts index d15162c336..787d613109 100644 --- a/packages/trpc/server/routers/viewer/slots.ts +++ b/packages/trpc/server/routers/viewer/slots.ts @@ -218,12 +218,16 @@ async function getEventType(ctx: { prisma: typeof prisma }, input: z.infer, ctx: } let currentSeats: CurrentSeats | undefined; - let users = eventType.users.map((user) => ({ + let usersWithCredentials = eventType.users.map((user) => ({ isFixed: !eventType.schedulingType || eventType.schedulingType === SchedulingType.COLLECTIVE, ...user, })); // overwrite if it is a team event & hosts is set, otherwise keep using users. if (eventType.schedulingType && !!eventType.hosts?.length) { - users = eventType.hosts.map(({ isFixed, user }) => ({ isFixed, ...user })); + usersWithCredentials = eventType.hosts.map(({ isFixed, user }) => ({ isFixed, ...user })); } /* We get all users working hours and busy slots */ const userAvailability = await Promise.all( - users.map(async (currentUser) => { + usersWithCredentials.map(async (currentUser) => { const { busy, workingHours, @@ -398,7 +403,7 @@ export async function getSchedule(input: z.infer, ctx: /* FIXME: For some reason this returns undefined while testing in Jest */ (await ctx.prisma.selectedSlots.findMany({ where: { - userId: { in: users.map((user) => user.id) }, + userId: { in: usersWithCredentials.map((user) => user.id) }, releaseAt: { gt: dayjs.utc().format() }, }, select: { @@ -527,13 +532,19 @@ export async function getSchedule(input: z.infer, ctx: ) => { // TODO: Adds unit tests to prevent regressions in getSchedule (try multiple timezones) const time = _time.tz(input.timeZone); + r[time.format("YYYY-MM-DD")] = r[time.format("YYYY-MM-DD")] || []; r[time.format("YYYY-MM-DD")].push({ ...passThroughProps, time: time.toISOString(), - users: (eventType.hosts ? eventType.hosts.map((host) => host.user) : eventType.users).map( - (user) => user.username || "" - ), + users: (eventType.hosts + ? eventType.hosts.map((hostUserWithCredentials) => { + const { user } = hostUserWithCredentials; + const { credentials: _credentials, ...hostUser } = user; + return hostUser; + }) + : eventType.users + ).map((user) => user.username || ""), // Conditionally add the attendees and booking id to slots object if there is already a booking during that time ...(currentSeats?.some((booking) => booking.startTime.toISOString() === time.toISOString()) && { attendees: diff --git a/packages/trpc/server/routers/viewer/teams.tsx b/packages/trpc/server/routers/viewer/teams.tsx index ff0acdc6fa..e48e15ee24 100644 --- a/packages/trpc/server/routers/viewer/teams.tsx +++ b/packages/trpc/server/routers/viewer/teams.tsx @@ -520,6 +520,7 @@ export const viewerTeamsRouter = router({ include: { user: { select: { + credentials: true, // needed for getUserAvailability ...availabilityUserSelect, }, },