diff --git a/apps/web/pages/apps/installed.tsx b/apps/web/pages/apps/installed.tsx index c39a0a6e..b524b0b2 100644 --- a/apps/web/pages/apps/installed.tsx +++ b/apps/web/pages/apps/installed.tsx @@ -112,7 +112,7 @@ function ConnectOrDisconnectIntegrationButton(props: { credentialIds: number[]; type: App["type"]; isGlobal?: boolean; - installed: boolean; + installed?: boolean; }) { const { t } = useLocale(); const [credentialId] = props.credentialIds; diff --git a/apps/web/pages/event-types/[type].tsx b/apps/web/pages/event-types/[type].tsx index 325910e1..92941ba8 100644 --- a/apps/web/pages/event-types/[type].tsx +++ b/apps/web/pages/event-types/[type].tsx @@ -2137,8 +2137,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => const t = await getTranslation(currentUser?.locale ?? "en", "common"); const integrations = getApps(credentials); const locationOptions = getLocationOptions(integrations, t); - - const hasPaymentIntegration = hasIntegration(integrations, "stripe_payment"); + const hasPaymentIntegration = !!credentials.find((credential) => credential.type === "stripe_payment"); const currency = (credentials.find((integration) => integration.type === "stripe_payment")?.key as unknown as StripeData) ?.default_currency || "usd"; diff --git a/packages/app-store/giphy/_metadata.ts b/packages/app-store/giphy/_metadata.ts index 57b2c259..15c6500f 100644 --- a/packages/app-store/giphy/_metadata.ts +++ b/packages/app-store/giphy/_metadata.ts @@ -5,7 +5,6 @@ import _package from "./package.json"; export const metadata = { name: "Giphy", description: _package.description, - installed: !!process.env.GIPHY_API_KEY, category: "other", // If using static next public folder, can then be referenced from the base URL (/). imageSrc: "/api/app-store/giphy/icon.svg", diff --git a/packages/app-store/hubspotothercalendar/_metadata.ts b/packages/app-store/hubspotothercalendar/_metadata.ts index 391ee990..52121027 100644 --- a/packages/app-store/hubspotothercalendar/_metadata.ts +++ b/packages/app-store/hubspotothercalendar/_metadata.ts @@ -5,7 +5,6 @@ import _package from "./package.json"; export const metadata = { name: "HubSpot CRM", description: _package.description, - installed: !!(process.env.HUBSPOT_CLIENT_ID && process.env.HUBSPOT_CLIENT_SECRET), type: "hubspot_other_calendar", imageSrc: "/api/app-store/hubspotothercalendar/icon.svg", variant: "other_calendar", diff --git a/packages/app-store/hubspotothercalendar/api/add.ts b/packages/app-store/hubspotothercalendar/api/add.ts index e2c7b704..d1c54ff4 100644 --- a/packages/app-store/hubspotothercalendar/api/add.ts +++ b/packages/app-store/hubspotothercalendar/api/add.ts @@ -3,20 +3,21 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { WEBAPP_URL } from "@calcom/lib/constants"; +import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; + const scopes = ["crm.objects.contacts.read", "crm.objects.contacts.write"]; -const client_id = process.env.HUBSPOT_CLIENT_ID; +let client_id = ""; const hubspotClient = new hubspot.Client(); export default async function handler(req: NextApiRequest, res: NextApiResponse) { - if (!client_id) { - res.status(400).json({ message: "HubSpot client id missing." }); - return; - } + if (req.method !== "GET") return res.status(405).json({ message: "Method not allowed" }); - if (req.method === "GET") { - const redirectUri = WEBAPP_URL + "/api/integrations/hubspotothercalendar/callback"; - const url = hubspotClient.oauth.getAuthorizationUrl(client_id, redirectUri, scopes.join(" ")); - res.status(200).json({ url }); - } + const appKeys = await getAppKeysFromSlug("hubspot"); + if (typeof appKeys.client_id === "string") client_id = appKeys.client_id; + if (!client_id) return res.status(400).json({ message: "HubSpot client id missing." }); + + const redirectUri = WEBAPP_URL + "/api/integrations/hubspotothercalendar/callback"; + const url = hubspotClient.oauth.getAuthorizationUrl(client_id, redirectUri, scopes.join(" ")); + res.status(200).json({ url }); } diff --git a/packages/app-store/office365calendar/_metadata.ts b/packages/app-store/office365calendar/_metadata.ts index aa44204d..645e0b89 100644 --- a/packages/app-store/office365calendar/_metadata.ts +++ b/packages/app-store/office365calendar/_metadata.ts @@ -5,7 +5,6 @@ import _package from "./package.json"; export const metadata = { name: "Office 365 / Outlook.com Calendar", description: _package.description, - installed: !!(process.env.MS_GRAPH_CLIENT_ID && process.env.MS_GRAPH_CLIENT_SECRET), type: "office365_calendar", title: "Office 365 / Outlook.com Calendar", imageSrc: "/api/app-store/office365calendar/icon.svg", diff --git a/packages/app-store/office365calendar/api/add.ts b/packages/app-store/office365calendar/api/add.ts index f174407a..686fd854 100644 --- a/packages/app-store/office365calendar/api/add.ts +++ b/packages/app-store/office365calendar/api/add.ts @@ -1,20 +1,26 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { stringify } from "querystring"; -import { BASE_URL } from "@calcom/lib/constants"; +import { WEBAPP_URL } from "@calcom/lib/constants"; import { encodeOAuthState } from "../../_utils/encodeOAuthState"; +import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; const scopes = ["User.Read", "Calendars.Read", "Calendars.ReadWrite", "offline_access"]; +let client_id = ""; + export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method === "GET") { + const appKeys = await getAppKeysFromSlug("office365-calendar"); + if (typeof appKeys.client_id === "string") client_id = appKeys.client_id; + if (!client_id) return res.status(400).json({ message: "Office 365 client_id missing." }); const state = encodeOAuthState(req); const params = { response_type: "code", scope: scopes.join(" "), - client_id: process.env.MS_GRAPH_CLIENT_ID, - redirect_uri: BASE_URL + "/api/integrations/office365calendar/callback", + client_id, + redirect_uri: WEBAPP_URL + "/api/integrations/office365calendar/callback", state, }; const query = stringify(params); diff --git a/packages/app-store/office365calendar/lib/CalendarService.ts b/packages/app-store/office365calendar/lib/CalendarService.ts index 63386928..848e4334 100644 --- a/packages/app-store/office365calendar/lib/CalendarService.ts +++ b/packages/app-store/office365calendar/lib/CalendarService.ts @@ -3,39 +3,40 @@ import { Credential } from "@prisma/client"; import { getLocation, getRichDescription } from "@calcom/lib/CalEventParser"; import { handleErrorsJson, handleErrorsRaw } from "@calcom/lib/errors"; +import { HttpError } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; import prisma from "@calcom/prisma"; import type { BufferedBusyTime } from "@calcom/types/BufferedBusyTime"; import type { Calendar, CalendarEvent, - IntegrationCalendar, - BatchResponse, EventBusyDate, + IntegrationCalendar, NewCalendarEventType, } from "@calcom/types/Calendar"; +import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; import { O365AuthCredentials } from "../types/Office365Calendar"; -const MS_GRAPH_CLIENT_ID = process.env.MS_GRAPH_CLIENT_ID || ""; -const MS_GRAPH_CLIENT_SECRET = process.env.MS_GRAPH_CLIENT_SECRET || ""; +let client_id = ""; +let client_secret = ""; export default class Office365CalendarService implements Calendar { private url = ""; private integrationName = ""; private log: typeof logger; - auth: { getToken: () => Promise }; + auth: Promise<{ getToken: () => Promise }>; constructor(credential: Credential) { this.integrationName = "office365_calendar"; - this.auth = this.o365Auth(credential); + this.auth = this.o365Auth(credential).then((t) => t); this.log = logger.getChildLogger({ prefix: [`[[lib] ${this.integrationName}`] }); } async createEvent(event: CalendarEvent): Promise { try { - const accessToken = await this.auth.getToken(); + const accessToken = await (await this.auth).getToken(); const calendarId = event.destinationCalendar?.externalId ? `${event.destinationCalendar.externalId}/` @@ -60,7 +61,7 @@ export default class Office365CalendarService implements Calendar { async updateEvent(uid: string, event: CalendarEvent): Promise { try { - const accessToken = await this.auth.getToken(); + const accessToken = await (await this.auth).getToken(); const response = await fetch("https://graph.microsoft.com/v1.0/me/calendar/events/" + uid, { method: "PATCH", @@ -81,7 +82,7 @@ export default class Office365CalendarService implements Calendar { async deleteEvent(uid: string): Promise { try { - const accessToken = await this.auth.getToken(); + const accessToken = await (await this.auth).getToken(); const response = await fetch("https://graph.microsoft.com/v1.0/me/calendar/events/" + uid, { method: "DELETE", @@ -109,9 +110,9 @@ export default class Office365CalendarService implements Calendar { const filter = `?startdatetime=${encodeURIComponent( dateFromParsed.toISOString() )}&enddatetime=${encodeURIComponent(dateToParsed.toISOString())}`; - return this.auth + return (await this.auth) .getToken() - .then((accessToken) => { + .then(async (accessToken) => { const selectedCalendarIds = selectedCalendars .filter((e) => e.integration === this.integrationName) .map((e) => e.externalId) @@ -121,50 +122,44 @@ export default class Office365CalendarService implements Calendar { return Promise.resolve([]); } - return ( - selectedCalendarIds.length === 0 - ? this.listCalendars().then((cals) => cals.map((e) => e.externalId).filter(Boolean) || []) - : Promise.resolve(selectedCalendarIds) - ).then((ids) => { - const requests = ids.map((calendarId, id) => ({ - id, - method: "GET", - url: `/me/calendars/${calendarId}/calendarView${filter}`, - })); - - return fetch("https://graph.microsoft.com/v1.0/$batch", { - method: "POST", - headers: { - Authorization: "Bearer " + accessToken, - "Content-Type": "application/json", - }, - body: JSON.stringify({ requests }), - }) - .then(handleErrorsJson) - .then((responseBody: BatchResponse) => - responseBody.responses.reduce( - (acc: BufferedBusyTime[], subResponse) => - acc.concat( - subResponse.body.value.map((evt) => { - return { - start: evt.start.dateTime + "Z", - end: evt.end.dateTime + "Z", - }; - }) - ), - [] - ) - ); + const ids = await (selectedCalendarIds.length === 0 + ? this.listCalendars().then((cals) => cals.map((e_2) => e_2.externalId).filter(Boolean) || []) + : Promise.resolve(selectedCalendarIds)); + const requests = ids.map((calendarId, id) => ({ + id, + method: "GET", + url: `/me/calendars/${calendarId}/calendarView${filter}`, + })); + const response = await fetch("https://graph.microsoft.com/v1.0/$batch", { + method: "POST", + headers: { + Authorization: "Bearer " + accessToken, + "Content-Type": "application/json", + }, + body: JSON.stringify({ requests }), }); + const responseBody = await handleErrorsJson(response); + return responseBody.responses.reduce( + (acc: BufferedBusyTime[], subResponse: { body: { value: any[] } }) => + acc.concat( + subResponse.body.value.map((evt) => { + return { + start: evt.start.dateTime + "Z", + end: evt.end.dateTime + "Z", + }; + }) + ), + [] + ); }) - .catch((err) => { + .catch((err: unknown) => { console.log(err); return Promise.reject([]); }); } async listCalendars(): Promise { - return this.auth.getToken().then((accessToken) => + return (await this.auth).getToken().then((accessToken) => fetch("https://graph.microsoft.com/v1.0/me/calendars", { method: "get", headers: { @@ -187,38 +182,41 @@ export default class Office365CalendarService implements Calendar { ); } - private o365Auth = (credential: Credential) => { + private o365Auth = async (credential: Credential) => { + const appKeys = await getAppKeysFromSlug("office365-calendar"); + if (typeof appKeys.client_id === "string") client_id = appKeys.client_id; + if (typeof appKeys.client_secret === "string") client_secret = appKeys.client_secret; + if (!client_id) throw new HttpError({ statusCode: 400, message: "office365 client_id missing." }); + if (!client_secret) throw new HttpError({ statusCode: 400, message: "office365 client_secret missing." }); + const isExpired = (expiryDate: number) => expiryDate < Math.round(+new Date() / 1000); const o365AuthCredentials = credential.key as O365AuthCredentials; - const refreshAccessToken = (refreshToken: string) => { - return fetch("https://login.microsoftonline.com/common/oauth2/v2.0/token", { + const refreshAccessToken = async (refreshToken: string) => { + const response = await fetch("https://login.microsoftonline.com/common/oauth2/v2.0/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ scope: "User.Read Calendars.Read Calendars.ReadWrite", - client_id: MS_GRAPH_CLIENT_ID, + client_id, refresh_token: refreshToken, grant_type: "refresh_token", - client_secret: MS_GRAPH_CLIENT_SECRET, + client_secret, }), - }) - .then(handleErrorsJson) - .then((responseBody) => { - o365AuthCredentials.access_token = responseBody.access_token; - o365AuthCredentials.expiry_date = Math.round(+new Date() / 1000 + responseBody.expires_in); - return prisma.credential - .update({ - where: { - id: credential.id, - }, - data: { - key: o365AuthCredentials, - }, - }) - .then(() => o365AuthCredentials.access_token); - }); + }); + const responseBody = await handleErrorsJson(response); + o365AuthCredentials.access_token = responseBody.access_token; + o365AuthCredentials.expiry_date = Math.round(+new Date() / 1000 + responseBody.expires_in); + await prisma.credential.update({ + where: { + id: credential.id, + }, + data: { + key: o365AuthCredentials, + }, + }); + return o365AuthCredentials.access_token; }; return { diff --git a/packages/app-store/office365video/_metadata.ts b/packages/app-store/office365video/_metadata.ts index dcc3d693..c4825514 100644 --- a/packages/app-store/office365video/_metadata.ts +++ b/packages/app-store/office365video/_metadata.ts @@ -6,7 +6,6 @@ import _package from "./package.json"; export const metadata = { name: "Microsoft 365/Teams", description: _package.description, - installed: !!(process.env.MS_GRAPH_CLIENT_ID && process.env.MS_GRAPH_CLIENT_SECRET), type: "office365_video", imageSrc: "/api/app-store/office365video/icon.svg", variant: "conferencing", diff --git a/packages/app-store/office365video/api/add.ts b/packages/app-store/office365video/api/add.ts index 142e3bb2..eeb7bf94 100644 --- a/packages/app-store/office365video/api/add.ts +++ b/packages/app-store/office365video/api/add.ts @@ -1,21 +1,26 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { stringify } from "querystring"; -import { BASE_URL } from "@calcom/lib/constants"; +import { WEBAPP_URL } from "@calcom/lib/constants"; import { encodeOAuthState } from "../../_utils/encodeOAuthState"; +import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; const scopes = ["OnlineMeetings.ReadWrite"]; +let client_id = ""; + export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method === "GET") { + const appKeys = await getAppKeysFromSlug("office365-calendar"); + if (typeof appKeys.client_id === "string") client_id = appKeys.client_id; + if (!client_id) return res.status(400).json({ message: "Office 365 client_id missing." }); const state = encodeOAuthState(req); - const params = { response_type: "code", scope: scopes.join(" "), - client_id: process.env.MS_GRAPH_CLIENT_ID, - redirect_uri: BASE_URL + "/api/integrations/office365video/callback", + client_id, + redirect_uri: WEBAPP_URL + "/api/integrations/office365video/callback", state, }; const query = stringify(params); diff --git a/packages/app-store/office365video/lib/VideoApiAdapter.ts b/packages/app-store/office365video/lib/VideoApiAdapter.ts index 37d2b6f9..a70bd039 100644 --- a/packages/app-store/office365video/lib/VideoApiAdapter.ts +++ b/packages/app-store/office365video/lib/VideoApiAdapter.ts @@ -1,13 +1,16 @@ import { Credential } from "@prisma/client"; import { handleErrorsJson, handleErrorsRaw } from "@calcom/lib/errors"; +import { HttpError } from "@calcom/lib/http-error"; import prisma from "@calcom/prisma"; import type { CalendarEvent } from "@calcom/types/Calendar"; import type { PartialReference } from "@calcom/types/EventManager"; import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter"; -const MS_GRAPH_CLIENT_ID = process.env.MS_GRAPH_CLIENT_ID || ""; -const MS_GRAPH_CLIENT_SECRET = process.env.MS_GRAPH_CLIENT_SECRET || ""; +import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; + +let client_id = ""; +let client_secret = ""; /** @link https://docs.microsoft.com/en-us/graph/api/application-post-onlinemeetings?view=graph-rest-1.0&tabs=http#response */ export interface TeamsEventResult { @@ -30,41 +33,45 @@ interface O365AuthCredentials { } // Checks to see if our O365 user token is valid or if we need to refresh -const o365Auth = (credential: Credential) => { +const o365Auth = async (credential: Credential) => { + const appKeys = await getAppKeysFromSlug("msteams"); + if (typeof appKeys.client_id === "string") client_id = appKeys.client_id; + if (typeof appKeys.client_secret === "string") client_secret = appKeys.client_secret; + if (!client_id) throw new HttpError({ statusCode: 400, message: "MS teams client_id missing." }); + if (!client_secret) throw new HttpError({ statusCode: 400, message: "MS teams client_secret missing." }); + const isExpired = (expiryDate: number) => expiryDate < Math.round(+new Date() / 1000); const o365AuthCredentials = credential.key as unknown as O365AuthCredentials; - const refreshAccessToken = (refreshToken: string) => { - return fetch("https://login.microsoftonline.com/common/oauth2/v2.0/token", { + const refreshAccessToken = async (refreshToken: string) => { + const response = await fetch("https://login.microsoftonline.com/common/oauth2/v2.0/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ scope: "User.Read Calendars.Read Calendars.ReadWrite", - client_id: MS_GRAPH_CLIENT_ID, + client_id, refresh_token: refreshToken, grant_type: "refresh_token", - client_secret: MS_GRAPH_CLIENT_SECRET, + client_secret, }), - }) - .then(handleErrorsJson) - .then(async (responseBody) => { - // set expiry date as offset from current time. - responseBody.expiry_date = Math.round(Date.now() + responseBody.expires_in * 1000); - delete responseBody.expires_in; - // Store new tokens in database. - await prisma.credential.update({ - where: { - id: credential.id, - }, - data: { - key: responseBody, - }, - }); - o365AuthCredentials.expiry_date = responseBody.expiry_date; - o365AuthCredentials.access_token = responseBody.access_token; - return o365AuthCredentials.access_token; - }); + }); + const responseBody = await handleErrorsJson(response); + // set expiry date as offset from current time. + responseBody.expiry_date = Math.round(Date.now() + responseBody.expires_in * 1000); + delete responseBody.expires_in; + // Store new tokens in database. + await prisma.credential.update({ + where: { + id: credential.id, + }, + data: { + key: responseBody, + }, + }); + o365AuthCredentials.expiry_date = responseBody.expiry_date; + o365AuthCredentials.access_token = responseBody.access_token; + return o365AuthCredentials.access_token; }; return { @@ -92,7 +99,7 @@ const TeamsVideoApiAdapter = (credential: Credential): VideoApiAdapter => { return Promise.resolve([]); }, updateMeeting: async (bookingRef: PartialReference, event: CalendarEvent) => { - const accessToken = await auth.getToken(); + const accessToken = await (await auth).getToken(); const resultString = await fetch("https://graph.microsoft.com/v1.0/me/onlineMeetings", { method: "POST", @@ -116,7 +123,7 @@ const TeamsVideoApiAdapter = (credential: Credential): VideoApiAdapter => { return Promise.resolve([]); }, createMeeting: async (event: CalendarEvent): Promise => { - const accessToken = await auth.getToken(); + const accessToken = await (await auth).getToken(); const resultString = await fetch("https://graph.microsoft.com/v1.0/me/onlineMeetings", { method: "POST", diff --git a/packages/app-store/utils.ts b/packages/app-store/utils.ts index 26fb109c..47f24cef 100644 --- a/packages/app-store/utils.ts +++ b/packages/app-store/utils.ts @@ -97,11 +97,6 @@ function getApps(userCredentials: CredentialData[]) { export type AppMeta = ReturnType; -/** @deprecated use `getApps` */ -export function hasIntegration(apps: AppMeta, type: string): boolean { - return !!apps.find((app) => app.type === type && !!app.installed && app.credentials.length > 0); -} - export function hasIntegrationInstalled(type: App["type"]): boolean { return ALL_APPS.some((app) => app.type === type && !!app.installed); } diff --git a/packages/app-store/zoomvideo/_metadata.ts b/packages/app-store/zoomvideo/_metadata.ts index 90f4dd4d..21fd8061 100644 --- a/packages/app-store/zoomvideo/_metadata.ts +++ b/packages/app-store/zoomvideo/_metadata.ts @@ -6,7 +6,6 @@ import _package from "./package.json"; export const metadata = { name: "Zoom Video", description: _package.description, - installed: !!(process.env.ZOOM_CLIENT_ID && process.env.ZOOM_CLIENT_SECRET), type: "zoom_video", imageSrc: "/api/app-store/zoomvideo/icon.svg", variant: "conferencing", diff --git a/packages/app-store/zoomvideo/api/add.ts b/packages/app-store/zoomvideo/api/add.ts index e04fd73e..826cb4b5 100644 --- a/packages/app-store/zoomvideo/api/add.ts +++ b/packages/app-store/zoomvideo/api/add.ts @@ -1,10 +1,12 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { stringify } from "querystring"; -import { BASE_URL } from "@calcom/lib/constants"; +import { WEBAPP_URL } from "@calcom/lib/constants"; import prisma from "@calcom/prisma"; -const client_id = process.env.ZOOM_CLIENT_ID; +import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; + +let client_id = ""; export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method === "GET") { @@ -19,10 +21,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) }, }); + const appKeys = await getAppKeysFromSlug("zoom"); + if (typeof appKeys.client_id === "string") client_id = appKeys.client_id; + if (!client_id) return res.status(400).json({ message: "Zoom client_id missing." }); + const params = { response_type: "code", client_id, - redirect_uri: BASE_URL + "/api/integrations/zoomvideo/callback", + redirect_uri: WEBAPP_URL + "/api/integrations/zoomvideo/callback", }; const query = stringify(params); const url = `https://zoom.us/oauth/authorize?${query}`; diff --git a/packages/app-store/zoomvideo/api/callback.ts b/packages/app-store/zoomvideo/api/callback.ts index 4bcce8fc..2e3fd823 100644 --- a/packages/app-store/zoomvideo/api/callback.ts +++ b/packages/app-store/zoomvideo/api/callback.ts @@ -1,15 +1,23 @@ import type { NextApiRequest, NextApiResponse } from "next"; -import { BASE_URL } from "@calcom/lib/constants"; +import { WEBAPP_URL } from "@calcom/lib/constants"; import prisma from "@calcom/prisma"; -const client_id = process.env.ZOOM_CLIENT_ID; -const client_secret = process.env.ZOOM_CLIENT_SECRET; +import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; + +let client_id = ""; +let client_secret = ""; export default async function handler(req: NextApiRequest, res: NextApiResponse) { const { code } = req.query; - const redirectUri = encodeURI(BASE_URL + "/api/integrations/zoomvideo/callback"); + const appKeys = await getAppKeysFromSlug("zoom"); + if (typeof appKeys.client_id === "string") client_id = appKeys.client_id; + if (typeof appKeys.client_secret === "string") client_secret = appKeys.client_secret; + if (!client_id) return res.status(400).json({ message: "Zoom client_id missing." }); + if (!client_secret) return res.status(400).json({ message: "Zoom client_secret missing." }); + + const redirectUri = encodeURI(WEBAPP_URL + "/api/integrations/zoomvideo/callback"); const authHeader = "Basic " + Buffer.from(client_id + ":" + client_secret).toString("base64"); const result = await fetch( "https://zoom.us/oauth/token?grant_type=authorization_code&code=" + code + "&redirect_uri=" + redirectUri, diff --git a/packages/app-store/zoomvideo/lib/VideoApiAdapter.ts b/packages/app-store/zoomvideo/lib/VideoApiAdapter.ts index 66caf0f0..ab63f8a8 100644 --- a/packages/app-store/zoomvideo/lib/VideoApiAdapter.ts +++ b/packages/app-store/zoomvideo/lib/VideoApiAdapter.ts @@ -1,11 +1,14 @@ import { Credential } from "@prisma/client"; import { handleErrorsJson, handleErrorsRaw } from "@calcom/lib/errors"; +import { HttpError } from "@calcom/lib/http-error"; import prisma from "@calcom/prisma"; import type { CalendarEvent } from "@calcom/types/Calendar"; import type { PartialReference } from "@calcom/types/EventManager"; import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter"; +import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; + /** @link https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingcreate */ export interface ZoomEventResult { password: string; @@ -67,13 +70,20 @@ interface ZoomToken { refresh_token: string; } -const zoomAuth = (credential: Credential) => { +let client_id = ""; +let client_secret = ""; + +const zoomAuth = async (credential: Credential) => { + const appKeys = await getAppKeysFromSlug("zoom"); + if (typeof appKeys.client_id === "string") client_id = appKeys.client_id; + if (typeof appKeys.client_secret === "string") client_secret = appKeys.client_secret; + if (!client_id) throw new HttpError({ statusCode: 400, message: "Zoom client_id missing." }); + if (!client_secret) throw new HttpError({ statusCode: 400, message: "Zoom client_secret missing." }); + const credentialKey = credential.key as unknown as ZoomToken; const isTokenValid = (token: ZoomToken) => token && token.token_type && token.access_token && (token.expires_in || token.expiry_date) < Date.now(); - const authHeader = - "Basic " + - Buffer.from(process.env.ZOOM_CLIENT_ID + ":" + process.env.ZOOM_CLIENT_SECRET).toString("base64"); + const authHeader = "Basic " + Buffer.from(client_id + ":" + client_secret).toString("base64"); const refreshAccessToken = (refreshToken: string) => fetch("https://zoom.us/oauth/token", { diff --git a/packages/types/App.d.ts b/packages/types/App.d.ts index e533a07b..9025f2a3 100644 --- a/packages/types/App.d.ts +++ b/packages/types/App.d.ts @@ -8,10 +8,11 @@ import type { LocationType } from "@calcom/app-store/locations"; */ export interface App { /** + * @deprecated * Wheter if the app is installed or not. Usually we check for api keys in env * variables to determine if this is true or not. * */ - installed: boolean; + installed?: boolean; /** The app type */ type: | `${string}_calendar`