App env fixes

This commit is contained in:
zomars 2022-05-02 17:13:34 -06:00
parent de4b3c186e
commit 6011b440a8
17 changed files with 169 additions and 138 deletions

View file

@ -112,7 +112,7 @@ function ConnectOrDisconnectIntegrationButton(props: {
credentialIds: number[]; credentialIds: number[];
type: App["type"]; type: App["type"];
isGlobal?: boolean; isGlobal?: boolean;
installed: boolean; installed?: boolean;
}) { }) {
const { t } = useLocale(); const { t } = useLocale();
const [credentialId] = props.credentialIds; const [credentialId] = props.credentialIds;

View file

@ -2137,8 +2137,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
const t = await getTranslation(currentUser?.locale ?? "en", "common"); const t = await getTranslation(currentUser?.locale ?? "en", "common");
const integrations = getApps(credentials); const integrations = getApps(credentials);
const locationOptions = getLocationOptions(integrations, t); const locationOptions = getLocationOptions(integrations, t);
const hasPaymentIntegration = !!credentials.find((credential) => credential.type === "stripe_payment");
const hasPaymentIntegration = hasIntegration(integrations, "stripe_payment");
const currency = const currency =
(credentials.find((integration) => integration.type === "stripe_payment")?.key as unknown as StripeData) (credentials.find((integration) => integration.type === "stripe_payment")?.key as unknown as StripeData)
?.default_currency || "usd"; ?.default_currency || "usd";

View file

@ -5,7 +5,6 @@ import _package from "./package.json";
export const metadata = { export const metadata = {
name: "Giphy", name: "Giphy",
description: _package.description, description: _package.description,
installed: !!process.env.GIPHY_API_KEY,
category: "other", category: "other",
// If using static next public folder, can then be referenced from the base URL (/). // If using static next public folder, can then be referenced from the base URL (/).
imageSrc: "/api/app-store/giphy/icon.svg", imageSrc: "/api/app-store/giphy/icon.svg",

View file

@ -5,7 +5,6 @@ import _package from "./package.json";
export const metadata = { export const metadata = {
name: "HubSpot CRM", name: "HubSpot CRM",
description: _package.description, description: _package.description,
installed: !!(process.env.HUBSPOT_CLIENT_ID && process.env.HUBSPOT_CLIENT_SECRET),
type: "hubspot_other_calendar", type: "hubspot_other_calendar",
imageSrc: "/api/app-store/hubspotothercalendar/icon.svg", imageSrc: "/api/app-store/hubspotothercalendar/icon.svg",
variant: "other_calendar", variant: "other_calendar",

View file

@ -3,20 +3,21 @@ import type { NextApiRequest, NextApiResponse } from "next";
import { WEBAPP_URL } from "@calcom/lib/constants"; import { WEBAPP_URL } from "@calcom/lib/constants";
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
const scopes = ["crm.objects.contacts.read", "crm.objects.contacts.write"]; 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(); const hubspotClient = new hubspot.Client();
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!client_id) { if (req.method !== "GET") return res.status(405).json({ message: "Method not allowed" });
res.status(400).json({ message: "HubSpot client id missing." });
return; 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." });
if (req.method === "GET") {
const redirectUri = WEBAPP_URL + "/api/integrations/hubspotothercalendar/callback"; const redirectUri = WEBAPP_URL + "/api/integrations/hubspotothercalendar/callback";
const url = hubspotClient.oauth.getAuthorizationUrl(client_id, redirectUri, scopes.join(" ")); const url = hubspotClient.oauth.getAuthorizationUrl(client_id, redirectUri, scopes.join(" "));
res.status(200).json({ url }); res.status(200).json({ url });
} }
}

View file

@ -5,7 +5,6 @@ import _package from "./package.json";
export const metadata = { export const metadata = {
name: "Office 365 / Outlook.com Calendar", name: "Office 365 / Outlook.com Calendar",
description: _package.description, description: _package.description,
installed: !!(process.env.MS_GRAPH_CLIENT_ID && process.env.MS_GRAPH_CLIENT_SECRET),
type: "office365_calendar", type: "office365_calendar",
title: "Office 365 / Outlook.com Calendar", title: "Office 365 / Outlook.com Calendar",
imageSrc: "/api/app-store/office365calendar/icon.svg", imageSrc: "/api/app-store/office365calendar/icon.svg",

View file

@ -1,20 +1,26 @@
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from "next";
import { stringify } from "querystring"; import { stringify } from "querystring";
import { BASE_URL } from "@calcom/lib/constants"; import { WEBAPP_URL } from "@calcom/lib/constants";
import { encodeOAuthState } from "../../_utils/encodeOAuthState"; import { encodeOAuthState } from "../../_utils/encodeOAuthState";
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
const scopes = ["User.Read", "Calendars.Read", "Calendars.ReadWrite", "offline_access"]; const scopes = ["User.Read", "Calendars.Read", "Calendars.ReadWrite", "offline_access"];
let client_id = "";
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "GET") { 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 state = encodeOAuthState(req);
const params = { const params = {
response_type: "code", response_type: "code",
scope: scopes.join(" "), scope: scopes.join(" "),
client_id: process.env.MS_GRAPH_CLIENT_ID, client_id,
redirect_uri: BASE_URL + "/api/integrations/office365calendar/callback", redirect_uri: WEBAPP_URL + "/api/integrations/office365calendar/callback",
state, state,
}; };
const query = stringify(params); const query = stringify(params);

View file

@ -3,39 +3,40 @@ import { Credential } from "@prisma/client";
import { getLocation, getRichDescription } from "@calcom/lib/CalEventParser"; import { getLocation, getRichDescription } from "@calcom/lib/CalEventParser";
import { handleErrorsJson, handleErrorsRaw } from "@calcom/lib/errors"; import { handleErrorsJson, handleErrorsRaw } from "@calcom/lib/errors";
import { HttpError } from "@calcom/lib/http-error";
import logger from "@calcom/lib/logger"; import logger from "@calcom/lib/logger";
import prisma from "@calcom/prisma"; import prisma from "@calcom/prisma";
import type { BufferedBusyTime } from "@calcom/types/BufferedBusyTime"; import type { BufferedBusyTime } from "@calcom/types/BufferedBusyTime";
import type { import type {
Calendar, Calendar,
CalendarEvent, CalendarEvent,
IntegrationCalendar,
BatchResponse,
EventBusyDate, EventBusyDate,
IntegrationCalendar,
NewCalendarEventType, NewCalendarEventType,
} from "@calcom/types/Calendar"; } from "@calcom/types/Calendar";
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
import { O365AuthCredentials } from "../types/Office365Calendar"; import { O365AuthCredentials } from "../types/Office365Calendar";
const MS_GRAPH_CLIENT_ID = process.env.MS_GRAPH_CLIENT_ID || ""; let client_id = "";
const MS_GRAPH_CLIENT_SECRET = process.env.MS_GRAPH_CLIENT_SECRET || ""; let client_secret = "";
export default class Office365CalendarService implements Calendar { export default class Office365CalendarService implements Calendar {
private url = ""; private url = "";
private integrationName = ""; private integrationName = "";
private log: typeof logger; private log: typeof logger;
auth: { getToken: () => Promise<string> }; auth: Promise<{ getToken: () => Promise<string> }>;
constructor(credential: Credential) { constructor(credential: Credential) {
this.integrationName = "office365_calendar"; 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}`] }); this.log = logger.getChildLogger({ prefix: [`[[lib] ${this.integrationName}`] });
} }
async createEvent(event: CalendarEvent): Promise<NewCalendarEventType> { async createEvent(event: CalendarEvent): Promise<NewCalendarEventType> {
try { try {
const accessToken = await this.auth.getToken(); const accessToken = await (await this.auth).getToken();
const calendarId = event.destinationCalendar?.externalId const calendarId = event.destinationCalendar?.externalId
? `${event.destinationCalendar.externalId}/` ? `${event.destinationCalendar.externalId}/`
@ -60,7 +61,7 @@ export default class Office365CalendarService implements Calendar {
async updateEvent(uid: string, event: CalendarEvent): Promise<any> { async updateEvent(uid: string, event: CalendarEvent): Promise<any> {
try { 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, { const response = await fetch("https://graph.microsoft.com/v1.0/me/calendar/events/" + uid, {
method: "PATCH", method: "PATCH",
@ -81,7 +82,7 @@ export default class Office365CalendarService implements Calendar {
async deleteEvent(uid: string): Promise<void> { async deleteEvent(uid: string): Promise<void> {
try { 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, { const response = await fetch("https://graph.microsoft.com/v1.0/me/calendar/events/" + uid, {
method: "DELETE", method: "DELETE",
@ -109,9 +110,9 @@ export default class Office365CalendarService implements Calendar {
const filter = `?startdatetime=${encodeURIComponent( const filter = `?startdatetime=${encodeURIComponent(
dateFromParsed.toISOString() dateFromParsed.toISOString()
)}&enddatetime=${encodeURIComponent(dateToParsed.toISOString())}`; )}&enddatetime=${encodeURIComponent(dateToParsed.toISOString())}`;
return this.auth return (await this.auth)
.getToken() .getToken()
.then((accessToken) => { .then(async (accessToken) => {
const selectedCalendarIds = selectedCalendars const selectedCalendarIds = selectedCalendars
.filter((e) => e.integration === this.integrationName) .filter((e) => e.integration === this.integrationName)
.map((e) => e.externalId) .map((e) => e.externalId)
@ -121,29 +122,25 @@ export default class Office365CalendarService implements Calendar {
return Promise.resolve([]); return Promise.resolve([]);
} }
return ( const ids = await (selectedCalendarIds.length === 0
selectedCalendarIds.length === 0 ? this.listCalendars().then((cals) => cals.map((e_2) => e_2.externalId).filter(Boolean) || [])
? this.listCalendars().then((cals) => cals.map((e) => e.externalId).filter(Boolean) || []) : Promise.resolve(selectedCalendarIds));
: Promise.resolve(selectedCalendarIds)
).then((ids) => {
const requests = ids.map((calendarId, id) => ({ const requests = ids.map((calendarId, id) => ({
id, id,
method: "GET", method: "GET",
url: `/me/calendars/${calendarId}/calendarView${filter}`, url: `/me/calendars/${calendarId}/calendarView${filter}`,
})); }));
const response = await fetch("https://graph.microsoft.com/v1.0/$batch", {
return fetch("https://graph.microsoft.com/v1.0/$batch", {
method: "POST", method: "POST",
headers: { headers: {
Authorization: "Bearer " + accessToken, Authorization: "Bearer " + accessToken,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ requests }), body: JSON.stringify({ requests }),
}) });
.then(handleErrorsJson) const responseBody = await handleErrorsJson(response);
.then((responseBody: BatchResponse) => return responseBody.responses.reduce(
responseBody.responses.reduce( (acc: BufferedBusyTime[], subResponse: { body: { value: any[] } }) =>
(acc: BufferedBusyTime[], subResponse) =>
acc.concat( acc.concat(
subResponse.body.value.map((evt) => { subResponse.body.value.map((evt) => {
return { return {
@ -153,18 +150,16 @@ export default class Office365CalendarService implements Calendar {
}) })
), ),
[] []
)
); );
});
}) })
.catch((err) => { .catch((err: unknown) => {
console.log(err); console.log(err);
return Promise.reject([]); return Promise.reject([]);
}); });
} }
async listCalendars(): Promise<IntegrationCalendar[]> { async listCalendars(): Promise<IntegrationCalendar[]> {
return this.auth.getToken().then((accessToken) => return (await this.auth).getToken().then((accessToken) =>
fetch("https://graph.microsoft.com/v1.0/me/calendars", { fetch("https://graph.microsoft.com/v1.0/me/calendars", {
method: "get", method: "get",
headers: { 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 isExpired = (expiryDate: number) => expiryDate < Math.round(+new Date() / 1000);
const o365AuthCredentials = credential.key as O365AuthCredentials; const o365AuthCredentials = credential.key as O365AuthCredentials;
const refreshAccessToken = (refreshToken: string) => { const refreshAccessToken = async (refreshToken: string) => {
return fetch("https://login.microsoftonline.com/common/oauth2/v2.0/token", { const response = await fetch("https://login.microsoftonline.com/common/oauth2/v2.0/token", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" }, headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({ body: new URLSearchParams({
scope: "User.Read Calendars.Read Calendars.ReadWrite", scope: "User.Read Calendars.Read Calendars.ReadWrite",
client_id: MS_GRAPH_CLIENT_ID, client_id,
refresh_token: refreshToken, refresh_token: refreshToken,
grant_type: "refresh_token", grant_type: "refresh_token",
client_secret: MS_GRAPH_CLIENT_SECRET, client_secret,
}), }),
}) });
.then(handleErrorsJson) const responseBody = await handleErrorsJson(response);
.then((responseBody) => {
o365AuthCredentials.access_token = responseBody.access_token; o365AuthCredentials.access_token = responseBody.access_token;
o365AuthCredentials.expiry_date = Math.round(+new Date() / 1000 + responseBody.expires_in); o365AuthCredentials.expiry_date = Math.round(+new Date() / 1000 + responseBody.expires_in);
return prisma.credential await prisma.credential.update({
.update({
where: { where: {
id: credential.id, id: credential.id,
}, },
data: { data: {
key: o365AuthCredentials, key: o365AuthCredentials,
}, },
})
.then(() => o365AuthCredentials.access_token);
}); });
return o365AuthCredentials.access_token;
}; };
return { return {

View file

@ -6,7 +6,6 @@ import _package from "./package.json";
export const metadata = { export const metadata = {
name: "Microsoft 365/Teams", name: "Microsoft 365/Teams",
description: _package.description, description: _package.description,
installed: !!(process.env.MS_GRAPH_CLIENT_ID && process.env.MS_GRAPH_CLIENT_SECRET),
type: "office365_video", type: "office365_video",
imageSrc: "/api/app-store/office365video/icon.svg", imageSrc: "/api/app-store/office365video/icon.svg",
variant: "conferencing", variant: "conferencing",

View file

@ -1,21 +1,26 @@
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from "next";
import { stringify } from "querystring"; import { stringify } from "querystring";
import { BASE_URL } from "@calcom/lib/constants"; import { WEBAPP_URL } from "@calcom/lib/constants";
import { encodeOAuthState } from "../../_utils/encodeOAuthState"; import { encodeOAuthState } from "../../_utils/encodeOAuthState";
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
const scopes = ["OnlineMeetings.ReadWrite"]; const scopes = ["OnlineMeetings.ReadWrite"];
let client_id = "";
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "GET") { 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 state = encodeOAuthState(req);
const params = { const params = {
response_type: "code", response_type: "code",
scope: scopes.join(" "), scope: scopes.join(" "),
client_id: process.env.MS_GRAPH_CLIENT_ID, client_id,
redirect_uri: BASE_URL + "/api/integrations/office365video/callback", redirect_uri: WEBAPP_URL + "/api/integrations/office365video/callback",
state, state,
}; };
const query = stringify(params); const query = stringify(params);

View file

@ -1,13 +1,16 @@
import { Credential } from "@prisma/client"; import { Credential } from "@prisma/client";
import { handleErrorsJson, handleErrorsRaw } from "@calcom/lib/errors"; import { handleErrorsJson, handleErrorsRaw } from "@calcom/lib/errors";
import { HttpError } from "@calcom/lib/http-error";
import prisma from "@calcom/prisma"; import prisma from "@calcom/prisma";
import type { CalendarEvent } from "@calcom/types/Calendar"; import type { CalendarEvent } from "@calcom/types/Calendar";
import type { PartialReference } from "@calcom/types/EventManager"; import type { PartialReference } from "@calcom/types/EventManager";
import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter"; import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter";
const MS_GRAPH_CLIENT_ID = process.env.MS_GRAPH_CLIENT_ID || ""; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
const MS_GRAPH_CLIENT_SECRET = process.env.MS_GRAPH_CLIENT_SECRET || "";
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 */ /** @link https://docs.microsoft.com/en-us/graph/api/application-post-onlinemeetings?view=graph-rest-1.0&tabs=http#response */
export interface TeamsEventResult { export interface TeamsEventResult {
@ -30,25 +33,30 @@ interface O365AuthCredentials {
} }
// Checks to see if our O365 user token is valid or if we need to refresh // 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 isExpired = (expiryDate: number) => expiryDate < Math.round(+new Date() / 1000);
const o365AuthCredentials = credential.key as unknown as O365AuthCredentials; const o365AuthCredentials = credential.key as unknown as O365AuthCredentials;
const refreshAccessToken = (refreshToken: string) => { const refreshAccessToken = async (refreshToken: string) => {
return fetch("https://login.microsoftonline.com/common/oauth2/v2.0/token", { const response = await fetch("https://login.microsoftonline.com/common/oauth2/v2.0/token", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" }, headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({ body: new URLSearchParams({
scope: "User.Read Calendars.Read Calendars.ReadWrite", scope: "User.Read Calendars.Read Calendars.ReadWrite",
client_id: MS_GRAPH_CLIENT_ID, client_id,
refresh_token: refreshToken, refresh_token: refreshToken,
grant_type: "refresh_token", grant_type: "refresh_token",
client_secret: MS_GRAPH_CLIENT_SECRET, client_secret,
}), }),
}) });
.then(handleErrorsJson) const responseBody = await handleErrorsJson(response);
.then(async (responseBody) => {
// set expiry date as offset from current time. // set expiry date as offset from current time.
responseBody.expiry_date = Math.round(Date.now() + responseBody.expires_in * 1000); responseBody.expiry_date = Math.round(Date.now() + responseBody.expires_in * 1000);
delete responseBody.expires_in; delete responseBody.expires_in;
@ -64,7 +72,6 @@ const o365Auth = (credential: Credential) => {
o365AuthCredentials.expiry_date = responseBody.expiry_date; o365AuthCredentials.expiry_date = responseBody.expiry_date;
o365AuthCredentials.access_token = responseBody.access_token; o365AuthCredentials.access_token = responseBody.access_token;
return o365AuthCredentials.access_token; return o365AuthCredentials.access_token;
});
}; };
return { return {
@ -92,7 +99,7 @@ const TeamsVideoApiAdapter = (credential: Credential): VideoApiAdapter => {
return Promise.resolve([]); return Promise.resolve([]);
}, },
updateMeeting: async (bookingRef: PartialReference, event: CalendarEvent) => { 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", { const resultString = await fetch("https://graph.microsoft.com/v1.0/me/onlineMeetings", {
method: "POST", method: "POST",
@ -116,7 +123,7 @@ const TeamsVideoApiAdapter = (credential: Credential): VideoApiAdapter => {
return Promise.resolve([]); return Promise.resolve([]);
}, },
createMeeting: async (event: CalendarEvent): Promise<VideoCallData> => { createMeeting: async (event: CalendarEvent): Promise<VideoCallData> => {
const accessToken = await auth.getToken(); const accessToken = await (await auth).getToken();
const resultString = await fetch("https://graph.microsoft.com/v1.0/me/onlineMeetings", { const resultString = await fetch("https://graph.microsoft.com/v1.0/me/onlineMeetings", {
method: "POST", method: "POST",

View file

@ -97,11 +97,6 @@ function getApps(userCredentials: CredentialData[]) {
export type AppMeta = ReturnType<typeof getApps>; export type AppMeta = ReturnType<typeof getApps>;
/** @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 { export function hasIntegrationInstalled(type: App["type"]): boolean {
return ALL_APPS.some((app) => app.type === type && !!app.installed); return ALL_APPS.some((app) => app.type === type && !!app.installed);
} }

View file

@ -6,7 +6,6 @@ import _package from "./package.json";
export const metadata = { export const metadata = {
name: "Zoom Video", name: "Zoom Video",
description: _package.description, description: _package.description,
installed: !!(process.env.ZOOM_CLIENT_ID && process.env.ZOOM_CLIENT_SECRET),
type: "zoom_video", type: "zoom_video",
imageSrc: "/api/app-store/zoomvideo/icon.svg", imageSrc: "/api/app-store/zoomvideo/icon.svg",
variant: "conferencing", variant: "conferencing",

View file

@ -1,10 +1,12 @@
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from "next";
import { stringify } from "querystring"; import { stringify } from "querystring";
import { BASE_URL } from "@calcom/lib/constants"; import { WEBAPP_URL } from "@calcom/lib/constants";
import prisma from "@calcom/prisma"; 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) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "GET") { 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 = { const params = {
response_type: "code", response_type: "code",
client_id, client_id,
redirect_uri: BASE_URL + "/api/integrations/zoomvideo/callback", redirect_uri: WEBAPP_URL + "/api/integrations/zoomvideo/callback",
}; };
const query = stringify(params); const query = stringify(params);
const url = `https://zoom.us/oauth/authorize?${query}`; const url = `https://zoom.us/oauth/authorize?${query}`;

View file

@ -1,15 +1,23 @@
import type { NextApiRequest, NextApiResponse } from "next"; 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"; import prisma from "@calcom/prisma";
const client_id = process.env.ZOOM_CLIENT_ID; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
const client_secret = process.env.ZOOM_CLIENT_SECRET;
let client_id = "";
let client_secret = "";
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { code } = req.query; 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 authHeader = "Basic " + Buffer.from(client_id + ":" + client_secret).toString("base64");
const result = await fetch( const result = await fetch(
"https://zoom.us/oauth/token?grant_type=authorization_code&code=" + code + "&redirect_uri=" + redirectUri, "https://zoom.us/oauth/token?grant_type=authorization_code&code=" + code + "&redirect_uri=" + redirectUri,

View file

@ -1,11 +1,14 @@
import { Credential } from "@prisma/client"; import { Credential } from "@prisma/client";
import { handleErrorsJson, handleErrorsRaw } from "@calcom/lib/errors"; import { handleErrorsJson, handleErrorsRaw } from "@calcom/lib/errors";
import { HttpError } from "@calcom/lib/http-error";
import prisma from "@calcom/prisma"; import prisma from "@calcom/prisma";
import type { CalendarEvent } from "@calcom/types/Calendar"; import type { CalendarEvent } from "@calcom/types/Calendar";
import type { PartialReference } from "@calcom/types/EventManager"; import type { PartialReference } from "@calcom/types/EventManager";
import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter"; 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 */ /** @link https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingcreate */
export interface ZoomEventResult { export interface ZoomEventResult {
password: string; password: string;
@ -67,13 +70,20 @@ interface ZoomToken {
refresh_token: string; 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 credentialKey = credential.key as unknown as ZoomToken;
const isTokenValid = (token: ZoomToken) => const isTokenValid = (token: ZoomToken) =>
token && token.token_type && token.access_token && (token.expires_in || token.expiry_date) < Date.now(); token && token.token_type && token.access_token && (token.expires_in || token.expiry_date) < Date.now();
const authHeader = const authHeader = "Basic " + Buffer.from(client_id + ":" + client_secret).toString("base64");
"Basic " +
Buffer.from(process.env.ZOOM_CLIENT_ID + ":" + process.env.ZOOM_CLIENT_SECRET).toString("base64");
const refreshAccessToken = (refreshToken: string) => const refreshAccessToken = (refreshToken: string) =>
fetch("https://zoom.us/oauth/token", { fetch("https://zoom.us/oauth/token", {

View file

@ -8,10 +8,11 @@ import type { LocationType } from "@calcom/app-store/locations";
*/ */
export interface App { export interface App {
/** /**
* @deprecated
* Wheter if the app is installed or not. Usually we check for api keys in env * 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. * variables to determine if this is true or not.
* */ * */
installed: boolean; installed?: boolean;
/** The app type */ /** The app type */
type: type:
| `${string}_calendar` | `${string}_calendar`