diff --git a/packages/app-store/hubspot/api/add.ts b/packages/app-store/hubspot/api/add.ts index 0cf581932b..a2af10fad6 100644 --- a/packages/app-store/hubspot/api/add.ts +++ b/packages/app-store/hubspot/api/add.ts @@ -3,6 +3,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { WEBAPP_URL } from "@calcom/lib/constants"; +import { encodeOAuthState } from "../../_utils/encodeOAuthState"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; const scopes = ["crm.objects.contacts.read", "crm.objects.contacts.write"]; @@ -18,6 +19,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) if (!client_id) return res.status(400).json({ message: "HubSpot client id missing." }); const redirectUri = WEBAPP_URL + "/api/integrations/hubspot/callback"; - const url = hubspotClient.oauth.getAuthorizationUrl(client_id, redirectUri, scopes.join(" ")); + const url = hubspotClient.oauth.getAuthorizationUrl( + client_id, + redirectUri, + scopes.join(" "), + undefined, + encodeOAuthState(req) + ); res.status(200).json({ url }); } diff --git a/packages/app-store/salesforce/api/add.ts b/packages/app-store/salesforce/api/add.ts index e56d882e21..1406b50f0a 100644 --- a/packages/app-store/salesforce/api/add.ts +++ b/packages/app-store/salesforce/api/add.ts @@ -3,6 +3,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { WEBAPP_URL } from "@calcom/lib/constants"; +import { encodeOAuthState } from "../../_utils/encodeOAuthState"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; let consumer_key = ""; @@ -19,6 +20,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) redirectUri: `${WEBAPP_URL}/api/integrations/salesforce/callback`, }); - const url = salesforceClient.oauth2.getAuthorizationUrl({ scope: "refresh_token full" }); + const url = salesforceClient.oauth2.getAuthorizationUrl({ + scope: "refresh_token full", + state: encodeOAuthState(req), + }); res.status(200).json({ url }); } diff --git a/packages/app-store/salesforce/lib/CalendarService.ts b/packages/app-store/salesforce/lib/CalendarService.ts index 4ab6c516b6..ab09fff1e7 100644 --- a/packages/app-store/salesforce/lib/CalendarService.ts +++ b/packages/app-store/salesforce/lib/CalendarService.ts @@ -79,7 +79,7 @@ export default class SalesforceCalendarService implements Calendar { .sobject("Contact") .create({ FirstName, - LastName, + LastName: LastName || "", Email: attendee.email, }) .then((result) => { diff --git a/packages/app-store/zoho-bigin/api/add.ts b/packages/app-store/zoho-bigin/api/add.ts index bb582ed545..a77721186e 100644 --- a/packages/app-store/zoho-bigin/api/add.ts +++ b/packages/app-store/zoho-bigin/api/add.ts @@ -3,6 +3,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { WEBAPP_URL } from "@calcom/lib/constants"; +import { encodeOAuthState } from "../../_utils/encodeOAuthState"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; import appConfig from "../config.json"; @@ -14,6 +15,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) if (!clientId) return res.status(400).json({ message: "Zoho Bigin client_id missing." }); const redirectUri = WEBAPP_URL + `/api/integrations/${appConfig.slug}/callback`; + const authUrl = axios.getUri({ url: "https://accounts.zoho.com/oauth/v2/auth", params: { @@ -22,6 +24,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) response_type: "code", redirect_uri: redirectUri, access_type: "offline", + state: encodeOAuthState(req), }, }); diff --git a/packages/app-store/zohocrm/api/_getAdd.ts b/packages/app-store/zohocrm/api/_getAdd.ts index 07b9991c48..edcc318885 100644 --- a/packages/app-store/zohocrm/api/_getAdd.ts +++ b/packages/app-store/zohocrm/api/_getAdd.ts @@ -2,6 +2,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { WEBAPP_URL } from "@calcom/lib/constants"; +import { encodeOAuthState } from "../../_utils/encodeOAuthState"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; let client_id = ""; @@ -10,8 +11,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const appKeys = await getAppKeysFromSlug("zohocrm"); if (typeof appKeys.client_id === "string") client_id = appKeys.client_id; if (!client_id) return res.status(400).json({ message: "zohocrm client id missing." }); + const state = encodeOAuthState(req); const redirectUri = WEBAPP_URL + "/api/integrations/zohocrm/callback"; - const url = `https://accounts.zoho.com/oauth/v2/auth?scope=ZohoCRM.modules.ALL,ZohoCRM.users.READ,AaaServer.profile.READ&client_id=${client_id}&response_type=code&access_type=offline&redirect_uri=${redirectUri}`; + const url = `https://accounts.zoho.com/oauth/v2/auth?scope=ZohoCRM.modules.ALL,ZohoCRM.users.READ,AaaServer.profile.READ&client_id=${client_id}&response_type=code&access_type=offline&redirect_uri=${redirectUri}&state=${state}`; res.status(200).json({ url }); } diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index 7019290e08..b6fc15f053 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -127,6 +127,64 @@ async function refreshCredentials(credentials: Array): Promise> +) => { + const allCredentials = user.credentials; + + // If it's a team event type query for team credentials + if (eventType.team?.id) { + const teamCredentialsQuery = await prisma.credential.findMany({ + where: { + teamId: eventType.team.id, + }, + }); + allCredentials.push(...teamCredentialsQuery); + } + + // If it's a managed event type, query for the parent team's credentials + if (eventType.parentId) { + const teamCredentialsQuery = await prisma.team.findFirst({ + where: { + eventTypes: { + some: { + id: eventType.parentId, + }, + }, + }, + select: { + credentials: true, + }, + }); + if (teamCredentialsQuery?.credentials) { + allCredentials.push(...teamCredentialsQuery?.credentials); + } + } + + // If the user is a part of an organization, query for the organization's credentials + if (user?.organizationId) { + const org = await prisma.team.findUnique({ + where: { + id: user.organizationId, + }, + select: { + credentials: true, + }, + }); + + if (org?.credentials) { + allCredentials.push(...org.credentials); + } + } + + return allCredentials; +}; + const isWithinAvailableHours = ( timeSlot: { start: ConfigType; end: ConfigType }, { @@ -267,6 +325,7 @@ const getEventTypesFromDB = async (eventTypeId: number) => { seatsShowAttendees: true, bookingLimits: true, durationLimits: true, + parentId: true, owner: { select: { hideBranding: true, @@ -837,6 +896,8 @@ async function handler( const [organizerUser] = users; const tOrganizer = await getTranslation(organizerUser?.locale ?? "en", "common"); + const allCredentials = await getAllCredentials(organizerUser, eventType); + // use host default if (isTeamEventType && locationBodyString === OrganizerDefaultConferencingAppType) { const metadataParseResult = userMetadataSchema.safeParse(organizerUser.metadata); @@ -1115,7 +1176,7 @@ async function handler( }, }); - const credentials = await refreshCredentials(organizerUser.credentials); + const credentials = await refreshCredentials(allCredentials); const eventManager = new EventManager({ ...organizerUser, credentials }); if (!originalRescheduledBooking) { @@ -1535,7 +1596,7 @@ async function handler( copyEvent.uid = booking.uid; await sendScheduledSeatsEmails(copyEvent, invitee[0], newSeat, !!eventType.seatsShowAttendees); - const credentials = await refreshCredentials(organizerUser.credentials); + const credentials = await refreshCredentials(allCredentials); const eventManager = new EventManager({ ...organizerUser, credentials }); await eventManager.updateCalendarAttendees(evt, booking); @@ -1839,7 +1900,7 @@ async function handler( } // After polling videoBusyTimes, credentials might have been changed due to refreshment, so query them again. - const credentials = await refreshCredentials(organizerUser.credentials); + const credentials = await refreshCredentials(allCredentials); const eventManager = new EventManager({ ...organizerUser, credentials }); function handleAppsStatus( diff --git a/packages/lib/defaultEvents.ts b/packages/lib/defaultEvents.ts index be8d3a1cb6..f7372f3f43 100644 --- a/packages/lib/defaultEvents.ts +++ b/packages/lib/defaultEvents.ts @@ -50,6 +50,7 @@ const user: User & { credentials: Credential[] } = { darkBrandColor: "#efefef", allowDynamicBooking: true, timeFormat: 12, + organizationId: null, }; const customInputs: CustomInputSchema[] = []; @@ -90,6 +91,7 @@ const commons = { durationLimits: null, hidden: false, userId: 0, + parentId: null, owner: null, workflows: [], users: [user], diff --git a/packages/prisma/selects/user.ts b/packages/prisma/selects/user.ts index 335c7e476b..8b42052884 100644 --- a/packages/prisma/selects/user.ts +++ b/packages/prisma/selects/user.ts @@ -46,6 +46,7 @@ export const userSelect = Prisma.validator()({ brandColor: true, darkBrandColor: true, metadata: true, + organizationId: true, ...availabilityUserSelect, }, });