diff --git a/.env.example b/.env.example index f6f6188a1b..3ab772cabb 100644 --- a/.env.example +++ b/.env.example @@ -230,19 +230,6 @@ AUTH_BEARER_TOKEN_VERCEL= E2E_TEST_APPLE_CALENDAR_EMAIL="" E2E_TEST_APPLE_CALENDAR_PASSWORD="" -# - APP CREDENTIAL SYNC *********************************************************************************** -# Used for self-hosters that are implementing Cal.com into their applications that already have certain integrations -# Under settings/admin/apps ensure that all app secrets are set the same as the parent application -# You can use: `openssl rand -base64 32` to generate one -CALCOM_WEBHOOK_SECRET="" -# This is the header name that will be used to verify the webhook secret. Should be in lowercase -CALCOM_WEBHOOK_HEADER_NAME="calcom-webhook-secret" -CALCOM_CREDENTIAL_SYNC_ENDPOINT="" -# Key should match on Cal.com and your application -# must be 32 bytes for AES256 encryption algorithm -# You can use: `openssl rand -base64 24` to generate one -CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY="" - # - OIDC E2E TEST ******************************************************************************************* # Ensure this ADMIN EMAIL is present in the SAML_ADMINS list @@ -256,4 +243,4 @@ E2E_TEST_OIDC_PROVIDER_DOMAIN= E2E_TEST_OIDC_USER_EMAIL= E2E_TEST_OIDC_USER_PASSWORD= -# *********************************************************************************************************** +# *********************************************************************************************************** \ No newline at end of file diff --git a/apps/web/pages/api/webhook/app-credential.ts b/apps/web/pages/api/webhook/app-credential.ts deleted file mode 100644 index 326cfb5b4d..0000000000 --- a/apps/web/pages/api/webhook/app-credential.ts +++ /dev/null @@ -1,93 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; -import z from "zod"; - -import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData"; -import { APP_CREDENTIAL_SHARING_ENABLED } from "@calcom/lib/constants"; -import { symmetricDecrypt } from "@calcom/lib/crypto"; -import prisma from "@calcom/prisma"; - -const appCredentialWebhookRequestBodySchema = z.object({ - // UserId of the cal.com user - userId: z.number().int(), - appSlug: z.string(), - // Keys should be AES256 encrypted with the CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY - keys: z.string(), -}); -/** */ -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - // Check that credential sharing is enabled - if (!APP_CREDENTIAL_SHARING_ENABLED) { - return res.status(403).json({ message: "Credential sharing is not enabled" }); - } - - // Check that the webhook secret matches - if ( - req.headers[process.env.CALCOM_WEBHOOK_HEADER_NAME || "calcom-webhook-secret"] !== - process.env.CALCOM_WEBHOOK_SECRET - ) { - return res.status(403).json({ message: "Invalid webhook secret" }); - } - - const reqBody = appCredentialWebhookRequestBodySchema.parse(req.body); - - // Check that the user exists - const user = await prisma.user.findUnique({ where: { id: reqBody.userId } }); - - if (!user) { - return res.status(404).json({ message: "User not found" }); - } - - const app = await prisma.app.findUnique({ - where: { slug: reqBody.appSlug }, - select: { slug: true }, - }); - - if (!app) { - return res.status(404).json({ message: "App not found" }); - } - - // Search for the app's slug and type - const appMetadata = appStoreMetadata[app.slug as keyof typeof appStoreMetadata]; - - if (!appMetadata) { - return res.status(404).json({ message: "App not found. Ensure that you have the correct app slug" }); - } - - // Decrypt the keys - const keys = JSON.parse( - symmetricDecrypt(reqBody.keys, process.env.CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY || "") - ); - - // Can't use prisma upsert as we don't know the id of the credential - const appCredential = await prisma.credential.findFirst({ - where: { - userId: reqBody.userId, - appId: appMetadata.slug, - }, - select: { - id: true, - }, - }); - - if (appCredential) { - await prisma.credential.update({ - where: { - id: appCredential.id, - }, - data: { - key: keys, - }, - }); - return res.status(200).json({ message: `Credentials updated for userId: ${reqBody.userId}` }); - } else { - await prisma.credential.create({ - data: { - key: keys, - userId: reqBody.userId, - appId: appMetadata.slug, - type: appMetadata.type, - }, - }); - return res.status(200).json({ message: `Credentials created for userId: ${reqBody.userId}` }); - } -} diff --git a/packages/app-store/_utils/oauth/createOAuthAppCredential.ts b/packages/app-store/_utils/createOAuthAppCredential.ts similarity index 92% rename from packages/app-store/_utils/oauth/createOAuthAppCredential.ts rename to packages/app-store/_utils/createOAuthAppCredential.ts index 3e334e534a..fdd3dbe025 100644 --- a/packages/app-store/_utils/oauth/createOAuthAppCredential.ts +++ b/packages/app-store/_utils/createOAuthAppCredential.ts @@ -3,8 +3,8 @@ import type { NextApiRequest } from "next"; import { HttpError } from "@calcom/lib/http-error"; import prisma from "@calcom/prisma"; -import { decodeOAuthState } from "../oauth/decodeOAuthState"; -import { throwIfNotHaveAdminAccessToTeam } from "../throwIfNotHaveAdminAccessToTeam"; +import { decodeOAuthState } from "./decodeOAuthState"; +import { throwIfNotHaveAdminAccessToTeam } from "./throwIfNotHaveAdminAccessToTeam"; /** * This function is used to create app credentials for either a user or a team diff --git a/packages/app-store/_utils/oauth/decodeOAuthState.ts b/packages/app-store/_utils/decodeOAuthState.ts similarity index 80% rename from packages/app-store/_utils/oauth/decodeOAuthState.ts rename to packages/app-store/_utils/decodeOAuthState.ts index c300a808c6..082d61177f 100644 --- a/packages/app-store/_utils/oauth/decodeOAuthState.ts +++ b/packages/app-store/_utils/decodeOAuthState.ts @@ -1,6 +1,6 @@ import type { NextApiRequest } from "next"; -import type { IntegrationOAuthCallbackState } from "../../types"; +import type { IntegrationOAuthCallbackState } from "../types"; export function decodeOAuthState(req: NextApiRequest) { if (typeof req.query.state !== "string") { diff --git a/packages/app-store/_utils/oauth/encodeOAuthState.ts b/packages/app-store/_utils/encodeOAuthState.ts similarity index 81% rename from packages/app-store/_utils/oauth/encodeOAuthState.ts rename to packages/app-store/_utils/encodeOAuthState.ts index 285642b8c8..03cfaafbbd 100644 --- a/packages/app-store/_utils/oauth/encodeOAuthState.ts +++ b/packages/app-store/_utils/encodeOAuthState.ts @@ -1,6 +1,6 @@ import type { NextApiRequest } from "next"; -import type { IntegrationOAuthCallbackState } from "../../types"; +import type { IntegrationOAuthCallbackState } from "../types"; export function encodeOAuthState(req: NextApiRequest) { if (typeof req.query.state !== "string") { diff --git a/packages/app-store/_utils/oauth/parseRefreshTokenResponse.ts b/packages/app-store/_utils/oauth/parseRefreshTokenResponse.ts deleted file mode 100644 index f852b71144..0000000000 --- a/packages/app-store/_utils/oauth/parseRefreshTokenResponse.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { z } from "zod"; - -import { APP_CREDENTIAL_SHARING_ENABLED } from "@calcom/lib/constants"; - -const minimumTokenResponseSchema = z.object({ - access_token: z.string(), - // Assume that any property with a number is the expiry - [z.string().toString()]: z.number(), - // Allow other properties in the token response - [z.string().optional().toString()]: z.unknown().optional(), -}); - -const parseRefreshTokenResponse = (response: any, schema: z.ZodTypeAny) => { - let refreshTokenResponse; - if (APP_CREDENTIAL_SHARING_ENABLED && process.env.CALCOM_CREDENTIAL_SYNC_ENDPOINT) { - refreshTokenResponse = minimumTokenResponseSchema.safeParse(response); - } else { - refreshTokenResponse = schema.safeParse(response); - } - - if (!refreshTokenResponse.success) { - throw new Error("Invalid refreshed tokens were returned"); - } - - if (!refreshTokenResponse.data.refresh_token) { - refreshTokenResponse.data.refresh_token = "refresh_token"; - } - - return refreshTokenResponse; -}; - -export default parseRefreshTokenResponse; diff --git a/packages/app-store/_utils/oauth/refreshOAuthTokens.ts b/packages/app-store/_utils/oauth/refreshOAuthTokens.ts deleted file mode 100644 index b667154c90..0000000000 --- a/packages/app-store/_utils/oauth/refreshOAuthTokens.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { APP_CREDENTIAL_SHARING_ENABLED } from "@calcom/lib/constants"; - -const refreshOAuthTokens = async (refreshFunction: () => any, appSlug: string, userId: number | null) => { - // Check that app syncing is enabled and that the credential belongs to a user - if (APP_CREDENTIAL_SHARING_ENABLED && process.env.CALCOM_CREDENTIAL_SYNC_ENDPOINT && userId) { - // Customize the payload based on what your endpoint requires - // The response should only contain the access token and expiry date - const response = await fetch(process.env.CALCOM_CREDENTIAL_SYNC_ENDPOINT, { - method: "POST", - body: new URLSearchParams({ - calcomUserId: userId.toString(), - appSlug, - }), - }); - return response; - } else { - const response = await refreshFunction(); - return response; - } -}; - -export default refreshOAuthTokens; diff --git a/packages/app-store/googlecalendar/api/add.ts b/packages/app-store/googlecalendar/api/add.ts index d8e66a4818..3f377c3eb4 100644 --- a/packages/app-store/googlecalendar/api/add.ts +++ b/packages/app-store/googlecalendar/api/add.ts @@ -3,8 +3,8 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { WEBAPP_URL_FOR_OAUTH } from "@calcom/lib/constants"; +import { encodeOAuthState } from "../../_utils/encodeOAuthState"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; -import { encodeOAuthState } from "../../_utils/oauth/encodeOAuthState"; const scopes = [ "https://www.googleapis.com/auth/calendar.readonly", diff --git a/packages/app-store/googlecalendar/api/callback.ts b/packages/app-store/googlecalendar/api/callback.ts index c65847396a..61c40be381 100644 --- a/packages/app-store/googlecalendar/api/callback.ts +++ b/packages/app-store/googlecalendar/api/callback.ts @@ -5,9 +5,9 @@ import { WEBAPP_URL_FOR_OAUTH, CAL_URL } from "@calcom/lib/constants"; import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; import prisma from "@calcom/prisma"; +import { decodeOAuthState } from "../../_utils/decodeOAuthState"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; -import { decodeOAuthState } from "../../_utils/oauth/decodeOAuthState"; let client_id = ""; let client_secret = ""; diff --git a/packages/app-store/googlecalendar/lib/CalendarService.ts b/packages/app-store/googlecalendar/lib/CalendarService.ts index a6a0c2f391..939d18e424 100644 --- a/packages/app-store/googlecalendar/lib/CalendarService.ts +++ b/packages/app-store/googlecalendar/lib/CalendarService.ts @@ -18,8 +18,6 @@ import type { } from "@calcom/types/Calendar"; import type { CredentialPayload } from "@calcom/types/Credential"; -import parseRefreshTokenResponse from "../../_utils/oauth/parseRefreshTokenResponse"; -import refreshOAuthTokens from "../../_utils/oauth/refreshOAuthTokens"; import { getGoogleAppKeys } from "./getGoogleAppKeys"; import { googleCredentialSchema } from "./googleCredentialSchema"; @@ -83,18 +81,11 @@ export default class GoogleCalendarService implements Calendar { const refreshAccessToken = async (myGoogleAuth: Awaited>) => { try { - const res = await refreshOAuthTokens( - async () => { - const fetchTokens = await myGoogleAuth.refreshToken(googleCredentials.refresh_token); - return fetchTokens.res; - }, - "google-calendar", - credential.userId - ); + const { res } = await myGoogleAuth.refreshToken(googleCredentials.refresh_token); const token = res?.data; googleCredentials.access_token = token.access_token; googleCredentials.expiry_date = token.expiry_date; - const key = parseRefreshTokenResponse(googleCredentials, googleCredentialSchema); + const key = googleCredentialSchema.parse(googleCredentials); await prisma.credential.update({ where: { id: credential.id }, data: { key }, diff --git a/packages/app-store/hubspot/api/add.ts b/packages/app-store/hubspot/api/add.ts index ed7956fce7..a2af10fad6 100644 --- a/packages/app-store/hubspot/api/add.ts +++ b/packages/app-store/hubspot/api/add.ts @@ -3,8 +3,8 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { WEBAPP_URL } from "@calcom/lib/constants"; +import { encodeOAuthState } from "../../_utils/encodeOAuthState"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; -import { encodeOAuthState } from "../../_utils/oauth/encodeOAuthState"; const scopes = ["crm.objects.contacts.read", "crm.objects.contacts.write"]; diff --git a/packages/app-store/hubspot/api/callback.ts b/packages/app-store/hubspot/api/callback.ts index 6321fce409..d119573a29 100644 --- a/packages/app-store/hubspot/api/callback.ts +++ b/packages/app-store/hubspot/api/callback.ts @@ -5,10 +5,10 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { WEBAPP_URL } from "@calcom/lib/constants"; import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; +import createOAuthAppCredential from "../../_utils/createOAuthAppCredential"; +import { decodeOAuthState } from "../../_utils/decodeOAuthState"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; -import createOAuthAppCredential from "../../_utils/oauth/createOAuthAppCredential"; -import { decodeOAuthState } from "../../_utils/oauth/decodeOAuthState"; let client_id = ""; let client_secret = ""; diff --git a/packages/app-store/hubspot/lib/CalendarService.ts b/packages/app-store/hubspot/lib/CalendarService.ts index 66095d6de0..b9817c6168 100644 --- a/packages/app-store/hubspot/lib/CalendarService.ts +++ b/packages/app-store/hubspot/lib/CalendarService.ts @@ -23,7 +23,6 @@ import type { import type { CredentialPayload } from "@calcom/types/Credential"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; -import refreshOAuthTokens from "../../_utils/oauth/refreshOAuthTokens"; import type { HubspotToken } from "../api/callback"; const hubspotClient = new hubspot.Client(); @@ -174,18 +173,13 @@ export default class HubspotCalendarService implements Calendar { const refreshAccessToken = async (refreshToken: string) => { try { - const hubspotRefreshToken: HubspotToken = await refreshOAuthTokens( - async () => - await hubspotClient.oauth.tokensApi.createToken( - "refresh_token", - undefined, - WEBAPP_URL + "/api/integrations/hubspot/callback", - this.client_id, - this.client_secret, - refreshToken - ), - "hubspot", - credential.userId + const hubspotRefreshToken: HubspotToken = await hubspotClient.oauth.tokensApi.createToken( + "refresh_token", + undefined, + WEBAPP_URL + "/api/integrations/hubspot/callback", + this.client_id, + this.client_secret, + refreshToken ); // set expiry date as offset from current time. diff --git a/packages/app-store/larkcalendar/api/add.ts b/packages/app-store/larkcalendar/api/add.ts index 5a1a5f4a3e..40b0ef79de 100644 --- a/packages/app-store/larkcalendar/api/add.ts +++ b/packages/app-store/larkcalendar/api/add.ts @@ -5,8 +5,8 @@ import { z } from "zod"; import { WEBAPP_URL } from "@calcom/lib/constants"; import { defaultHandler, defaultResponder } from "@calcom/lib/server"; +import { encodeOAuthState } from "../../_utils/encodeOAuthState"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; -import { encodeOAuthState } from "../../_utils/oauth/encodeOAuthState"; import { LARK_HOST } from "../common"; const larkKeysSchema = z.object({ diff --git a/packages/app-store/larkcalendar/api/callback.ts b/packages/app-store/larkcalendar/api/callback.ts index accb63d9a6..080aa4c331 100644 --- a/packages/app-store/larkcalendar/api/callback.ts +++ b/packages/app-store/larkcalendar/api/callback.ts @@ -6,8 +6,8 @@ import logger from "@calcom/lib/logger"; import { defaultHandler, defaultResponder } from "@calcom/lib/server"; import prisma from "@calcom/prisma"; +import { decodeOAuthState } from "../../_utils/decodeOAuthState"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; -import { decodeOAuthState } from "../../_utils/oauth/decodeOAuthState"; import { LARK_HOST } from "../common"; import { getAppAccessToken } from "../lib/AppAccessToken"; import type { LarkAuthCredentials } from "../types/LarkCalendar"; diff --git a/packages/app-store/larkcalendar/lib/CalendarService.ts b/packages/app-store/larkcalendar/lib/CalendarService.ts index 6e3edaf0bf..88dba3fc25 100644 --- a/packages/app-store/larkcalendar/lib/CalendarService.ts +++ b/packages/app-store/larkcalendar/lib/CalendarService.ts @@ -11,7 +11,6 @@ import type { } from "@calcom/types/Calendar"; import type { CredentialPayload } from "@calcom/types/Credential"; -import refreshOAuthTokens from "../../_utils/oauth/refreshOAuthTokens"; import { handleLarkError, isExpired, LARK_HOST } from "../common"; import type { CreateAttendeesResp, @@ -64,22 +63,17 @@ export default class LarkCalendarService implements Calendar { } try { const appAccessToken = await getAppAccessToken(); - const resp = await refreshOAuthTokens( - async () => - await fetch(`${this.url}/authen/v1/refresh_access_token`, { - method: "POST", - headers: { - Authorization: `Bearer ${appAccessToken}`, - "Content-Type": "application/json; charset=utf-8", - }, - body: JSON.stringify({ - grant_type: "refresh_token", - refresh_token: refreshToken, - }), - }), - "lark-calendar", - credential.userId - ); + const resp = await fetch(`${this.url}/authen/v1/refresh_access_token`, { + method: "POST", + headers: { + Authorization: `Bearer ${appAccessToken}`, + "Content-Type": "application/json; charset=utf-8", + }, + body: JSON.stringify({ + grant_type: "refresh_token", + refresh_token: refreshToken, + }), + }); const data = await handleLarkError(resp, this.log); this.log.debug( diff --git a/packages/app-store/office365calendar/api/add.ts b/packages/app-store/office365calendar/api/add.ts index 0cdeafaa2e..686fd8544d 100644 --- a/packages/app-store/office365calendar/api/add.ts +++ b/packages/app-store/office365calendar/api/add.ts @@ -3,8 +3,8 @@ import { stringify } from "querystring"; import { WEBAPP_URL } from "@calcom/lib/constants"; +import { encodeOAuthState } from "../../_utils/encodeOAuthState"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; -import { encodeOAuthState } from "../../_utils/oauth/encodeOAuthState"; const scopes = ["User.Read", "Calendars.Read", "Calendars.ReadWrite", "offline_access"]; diff --git a/packages/app-store/office365calendar/api/callback.ts b/packages/app-store/office365calendar/api/callback.ts index 17ae98c4f5..b1ce89937f 100644 --- a/packages/app-store/office365calendar/api/callback.ts +++ b/packages/app-store/office365calendar/api/callback.ts @@ -4,9 +4,9 @@ import { WEBAPP_URL } from "@calcom/lib/constants"; import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; import prisma from "@calcom/prisma"; +import { decodeOAuthState } from "../../_utils/decodeOAuthState"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; -import { decodeOAuthState } from "../../_utils/oauth/decodeOAuthState"; const scopes = ["offline_access", "Calendars.Read", "Calendars.ReadWrite"]; diff --git a/packages/app-store/office365calendar/lib/CalendarService.ts b/packages/app-store/office365calendar/lib/CalendarService.ts index 5742fde8f4..39e6ceb985 100644 --- a/packages/app-store/office365calendar/lib/CalendarService.ts +++ b/packages/app-store/office365calendar/lib/CalendarService.ts @@ -17,8 +17,6 @@ import type { } from "@calcom/types/Calendar"; import type { CredentialPayload } from "@calcom/types/Credential"; -import parseRefreshTokenResponse from "../../_utils/oauth/parseRefreshTokenResponse"; -import refreshOAuthTokens from "../../_utils/oauth/refreshOAuthTokens"; import type { O365AuthCredentials } from "../types/Office365Calendar"; import { getOfficeAppKeys } from "./getOfficeAppKeys"; @@ -243,25 +241,28 @@ export default class Office365CalendarService implements Calendar { const refreshAccessToken = async (o365AuthCredentials: O365AuthCredentials) => { const { client_id, client_secret } = await getOfficeAppKeys(); - const response = await refreshOAuthTokens( - async () => - 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, - refresh_token: o365AuthCredentials.refresh_token, - grant_type: "refresh_token", - client_secret, - }), - }), - "office365-calendar", - credential.userId - ); + 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, + refresh_token: o365AuthCredentials.refresh_token, + grant_type: "refresh_token", + client_secret, + }), + }); const responseJson = await handleErrorsJson(response); - const tokenResponse = parseRefreshTokenResponse(responseJson, refreshTokenResponseSchema); + const tokenResponse = refreshTokenResponseSchema.safeParse(responseJson); o365AuthCredentials = { ...o365AuthCredentials, ...(tokenResponse.success && tokenResponse.data) }; + if (!tokenResponse.success) { + console.error( + "Outlook error grabbing new tokens ~ zodError:", + tokenResponse.error, + "MS response:", + responseJson + ); + } await prisma.credential.update({ where: { id: credential.id, diff --git a/packages/app-store/office365video/api/add.ts b/packages/app-store/office365video/api/add.ts index 2f3424cb2e..f1a3e622bd 100644 --- a/packages/app-store/office365video/api/add.ts +++ b/packages/app-store/office365video/api/add.ts @@ -3,8 +3,8 @@ import { stringify } from "querystring"; import { WEBAPP_URL } from "@calcom/lib/constants"; +import { encodeOAuthState } from "../../_utils/encodeOAuthState"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; -import { encodeOAuthState } from "../../_utils/oauth/encodeOAuthState"; const scopes = ["OnlineMeetings.ReadWrite", "offline_access"]; diff --git a/packages/app-store/office365video/api/callback.ts b/packages/app-store/office365video/api/callback.ts index 27211f3dea..2a9c8bafe4 100644 --- a/packages/app-store/office365video/api/callback.ts +++ b/packages/app-store/office365video/api/callback.ts @@ -4,10 +4,10 @@ import { WEBAPP_URL } from "@calcom/lib/constants"; import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; import prisma from "@calcom/prisma"; +import createOAuthAppCredential from "../../_utils/createOAuthAppCredential"; +import { decodeOAuthState } from "../../_utils/decodeOAuthState"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; -import createOAuthAppCredential from "../../_utils/oauth/createOAuthAppCredential"; -import { decodeOAuthState } from "../../_utils/oauth/decodeOAuthState"; const scopes = ["OnlineMeetings.ReadWrite", "offline_access"]; diff --git a/packages/app-store/office365video/lib/VideoApiAdapter.ts b/packages/app-store/office365video/lib/VideoApiAdapter.ts index 06521d1123..44c096b4aa 100644 --- a/packages/app-store/office365video/lib/VideoApiAdapter.ts +++ b/packages/app-store/office365video/lib/VideoApiAdapter.ts @@ -9,7 +9,6 @@ import type { PartialReference } from "@calcom/types/EventManager"; import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; -import refreshOAuthTokens from "../../_utils/oauth/refreshOAuthTokens"; let client_id = ""; let client_secret = ""; @@ -58,21 +57,16 @@ const o365Auth = async (credential: CredentialPayload) => { const o365AuthCredentials = credential.key as unknown as O365AuthCredentials; const refreshAccessToken = async (refreshToken: string) => { - const response = await refreshOAuthTokens( - async () => - await fetch("https://login.microsoftonline.com/common/oauth2/v2.0/token", { - method: "POST", - headers: { "Content-Type": "application/x-www-form-urlencoded" }, - body: new URLSearchParams({ - client_id, - refresh_token: refreshToken, - grant_type: "refresh_token", - client_secret, - }), - }), - "msteams", - credential.userId - ); + 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({ + client_id, + refresh_token: refreshToken, + grant_type: "refresh_token", + client_secret, + }), + }); const responseBody = await handleErrorsJson(response); diff --git a/packages/app-store/salesforce/api/add.ts b/packages/app-store/salesforce/api/add.ts index 907afc723a..1406b50f0a 100644 --- a/packages/app-store/salesforce/api/add.ts +++ b/packages/app-store/salesforce/api/add.ts @@ -3,8 +3,8 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { WEBAPP_URL } from "@calcom/lib/constants"; +import { encodeOAuthState } from "../../_utils/encodeOAuthState"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; -import { encodeOAuthState } from "../../_utils/oauth/encodeOAuthState"; let consumer_key = ""; diff --git a/packages/app-store/salesforce/api/callback.ts b/packages/app-store/salesforce/api/callback.ts index 739dbc2d96..7239406a5a 100644 --- a/packages/app-store/salesforce/api/callback.ts +++ b/packages/app-store/salesforce/api/callback.ts @@ -4,10 +4,10 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { WEBAPP_URL } from "@calcom/lib/constants"; import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; +import createOAuthAppCredential from "../../_utils/createOAuthAppCredential"; +import { decodeOAuthState } from "../../_utils/decodeOAuthState"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; -import createOAuthAppCredential from "../../_utils/oauth/createOAuthAppCredential"; -import { decodeOAuthState } from "../../_utils/oauth/decodeOAuthState"; let consumer_key = ""; let consumer_secret = ""; diff --git a/packages/app-store/salesforce/lib/CalendarService.ts b/packages/app-store/salesforce/lib/CalendarService.ts index 566d5dfb3c..4b16bb4c4d 100644 --- a/packages/app-store/salesforce/lib/CalendarService.ts +++ b/packages/app-store/salesforce/lib/CalendarService.ts @@ -1,7 +1,6 @@ import type { TokenResponse } from "jsforce"; import jsforce from "jsforce"; import { RRule } from "rrule"; -import { z } from "zod"; import { getLocation } from "@calcom/lib/CalEventParser"; import { WEBAPP_URL } from "@calcom/lib/constants"; @@ -17,7 +16,6 @@ import type { import type { CredentialPayload } from "@calcom/types/Credential"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; -import parseRefreshTokenResponse from "../../_utils/oauth/parseRefreshTokenResponse"; type ExtendedTokenResponse = TokenResponse & { instance_url: string; @@ -36,16 +34,6 @@ const sfApiErrors = { INVALID_EVENTWHOIDS: "INVALID_FIELD: No such column 'EventWhoIds' on sobject of type Event", }; -const salesforceTokenSchema = z.object({ - id: z.string(), - issued_at: z.string(), - instance_url: z.string(), - signature: z.string(), - access_token: z.string(), - scope: z.string(), - token_type: z.string(), -}); - export default class SalesforceCalendarService implements Calendar { private integrationName = ""; private conn: Promise; @@ -72,32 +60,6 @@ export default class SalesforceCalendarService implements Calendar { const credentialKey = credential.key as unknown as ExtendedTokenResponse; - const response = await fetch("https://login.salesforce.com/services/oauth2/token", { - method: "POST", - body: new URLSearchParams({ - grant_type: "refresh_token", - client_id: consumer_key, - client_secret: consumer_secret, - refresh_token: credentialKey.refresh_token, - format: "json", - }), - }); - - if (response.statusText !== "OK") throw new HttpError({ statusCode: 400, message: response.statusText }); - - const accessTokenJson = await response.json(); - - const accessTokenParsed = parseRefreshTokenResponse(accessTokenJson, salesforceTokenSchema); - - if (!accessTokenParsed.success) { - return Promise.reject(new Error("Invalid refreshed tokens were returned")); - } - - await prisma.credential.update({ - where: { id: credential.id }, - data: { key: { ...accessTokenParsed.data, refresh_token: credentialKey.refresh_token } }, - }); - return new jsforce.Connection({ clientId: consumer_key, clientSecret: consumer_secret, diff --git a/packages/app-store/stripepayment/api/callback.ts b/packages/app-store/stripepayment/api/callback.ts index 95c86fe408..1ef7c27430 100644 --- a/packages/app-store/stripepayment/api/callback.ts +++ b/packages/app-store/stripepayment/api/callback.ts @@ -2,8 +2,8 @@ import type { Prisma } from "@prisma/client"; import type { NextApiRequest, NextApiResponse } from "next"; import { stringify } from "querystring"; +import createOAuthAppCredential from "../../_utils/createOAuthAppCredential"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; -import createOAuthAppCredential from "../../_utils/oauth/createOAuthAppCredential"; import type { StripeData } from "../lib/server"; import stripe from "../lib/server"; diff --git a/packages/app-store/tandemvideo/api/callback.ts b/packages/app-store/tandemvideo/api/callback.ts index 7d94f7dc17..f5275f4f74 100644 --- a/packages/app-store/tandemvideo/api/callback.ts +++ b/packages/app-store/tandemvideo/api/callback.ts @@ -2,9 +2,9 @@ import type { NextApiRequest, NextApiResponse } from "next"; import prisma from "@calcom/prisma"; +import createOAuthAppCredential from "../../_utils/createOAuthAppCredential"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; -import createOAuthAppCredential from "../../_utils/oauth/createOAuthAppCredential"; let client_id = ""; let client_secret = ""; diff --git a/packages/app-store/webex/api/callback.ts b/packages/app-store/webex/api/callback.ts index 7ad6017168..b2f5bad081 100644 --- a/packages/app-store/webex/api/callback.ts +++ b/packages/app-store/webex/api/callback.ts @@ -3,8 +3,8 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { WEBAPP_URL } from "@calcom/lib/constants"; import prisma from "@calcom/prisma"; +import createOAuthAppCredential from "../../_utils/createOAuthAppCredential"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; -import createOAuthAppCredential from "../../_utils/oauth/createOAuthAppCredential"; import config from "../config.json"; import { getWebexAppKeys } from "../lib/getWebexAppKeys"; diff --git a/packages/app-store/webex/lib/VideoApiAdapter.ts b/packages/app-store/webex/lib/VideoApiAdapter.ts index 37b0d3b199..f7f4729114 100644 --- a/packages/app-store/webex/lib/VideoApiAdapter.ts +++ b/packages/app-store/webex/lib/VideoApiAdapter.ts @@ -8,7 +8,6 @@ import type { CredentialPayload } from "@calcom/types/Credential"; import type { PartialReference } from "@calcom/types/EventManager"; import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter"; -import refreshOAuthTokens from "../../_utils/oauth/refreshOAuthTokens"; import { getWebexAppKeys } from "./getWebexAppKeys"; /** @link https://developer.webex.com/docs/meetings **/ @@ -59,23 +58,18 @@ const webexAuth = (credential: CredentialPayload) => { const refreshAccessToken = async (refreshToken: string) => { const { client_id, client_secret } = await getWebexAppKeys(); - const response = await refreshOAuthTokens( - async () => - await fetch("https://webexapis.com/v1/access_token", { - method: "POST", - headers: { - "Content-type": "application/x-www-form-urlencoded", - }, - body: new URLSearchParams({ - grant_type: "refresh_token", - client_id: client_id, - client_secret: client_secret, - refresh_token: refreshToken, - }), - }), - "webex", - credential.userId - ); + const response = await fetch("https://webexapis.com/v1/access_token", { + method: "POST", + headers: { + "Content-type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ + grant_type: "refresh_token", + client_id: client_id, + client_secret: client_secret, + refresh_token: refreshToken, + }), + }); const responseBody = await handleWebexResponse(response, credential.id); diff --git a/packages/app-store/zoho-bigin/api/add.ts b/packages/app-store/zoho-bigin/api/add.ts index 807321df71..a77721186e 100644 --- a/packages/app-store/zoho-bigin/api/add.ts +++ b/packages/app-store/zoho-bigin/api/add.ts @@ -3,8 +3,8 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { WEBAPP_URL } from "@calcom/lib/constants"; +import { encodeOAuthState } from "../../_utils/encodeOAuthState"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; -import { encodeOAuthState } from "../../_utils/oauth/encodeOAuthState"; import appConfig from "../config.json"; export default async function handler(req: NextApiRequest, res: NextApiResponse) { @@ -14,7 +14,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const clientId = typeof appKeys.client_id === "string" ? appKeys.client_id : ""; if (!clientId) return res.status(400).json({ message: "Zoho Bigin client_id missing." }); - const redirectUri = WEBAPP_URL + `/api/integrations/zoho-bigin/callback`; + const redirectUri = WEBAPP_URL + `/api/integrations/${appConfig.slug}/callback`; const authUrl = axios.getUri({ url: "https://accounts.zoho.com/oauth/v2/auth", diff --git a/packages/app-store/zoho-bigin/api/callback.ts b/packages/app-store/zoho-bigin/api/callback.ts index 85f5487d01..c8f219045f 100644 --- a/packages/app-store/zoho-bigin/api/callback.ts +++ b/packages/app-store/zoho-bigin/api/callback.ts @@ -5,10 +5,10 @@ import qs from "qs"; import { WEBAPP_URL } from "@calcom/lib/constants"; import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; +import createOAuthAppCredential from "../../_utils/createOAuthAppCredential"; +import { decodeOAuthState } from "../../_utils/decodeOAuthState"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; -import createOAuthAppCredential from "../../_utils/oauth/createOAuthAppCredential"; -import { decodeOAuthState } from "../../_utils/oauth/decodeOAuthState"; import appConfig from "../config.json"; export default async function handler(req: NextApiRequest, res: NextApiResponse) { diff --git a/packages/app-store/zoho-bigin/lib/CalendarService.ts b/packages/app-store/zoho-bigin/lib/CalendarService.ts index 676c8062ef..7f1ba1625b 100644 --- a/packages/app-store/zoho-bigin/lib/CalendarService.ts +++ b/packages/app-store/zoho-bigin/lib/CalendarService.ts @@ -15,7 +15,6 @@ import type { import type { CredentialPayload } from "@calcom/types/Credential"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; -import refreshOAuthTokens from "../../_utils/oauth/refreshOAuthTokens"; import { appKeysSchema } from "../zod"; export type BiginToken = { @@ -82,16 +81,11 @@ export default class BiginCalendarService implements Calendar { refresh_token: credentialKey.refresh_token, }; - const tokenInfo = await refreshOAuthTokens( - async () => - await axios.post(accountsUrl, qs.stringify(formData), { - headers: { - "Content-Type": "application/x-www-form-urlencoded;charset=utf-8", - }, - }), - "zoho-bigin", - credentialId - ); + const tokenInfo = await axios.post(accountsUrl, qs.stringify(formData), { + headers: { + "Content-Type": "application/x-www-form-urlencoded;charset=utf-8", + }, + }); if (!tokenInfo.data.error) { // set expiry date as offset from current time. diff --git a/packages/app-store/zohocrm/api/_getAdd.ts b/packages/app-store/zohocrm/api/_getAdd.ts index 34fcde771d..cef586ac04 100644 --- a/packages/app-store/zohocrm/api/_getAdd.ts +++ b/packages/app-store/zohocrm/api/_getAdd.ts @@ -3,8 +3,8 @@ import { stringify } from "querystring"; import { WEBAPP_URL } from "@calcom/lib/constants"; +import { encodeOAuthState } from "../../_utils/encodeOAuthState"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; -import { encodeOAuthState } from "../../_utils/oauth/encodeOAuthState"; let client_id = ""; diff --git a/packages/app-store/zohocrm/api/callback.ts b/packages/app-store/zohocrm/api/callback.ts index 224157a34c..32ae5bbb1b 100644 --- a/packages/app-store/zohocrm/api/callback.ts +++ b/packages/app-store/zohocrm/api/callback.ts @@ -5,10 +5,10 @@ import qs from "qs"; import { WEBAPP_URL } from "@calcom/lib/constants"; import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; +import createOAuthAppCredential from "../../_utils/createOAuthAppCredential"; +import { decodeOAuthState } from "../../_utils/decodeOAuthState"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; -import createOAuthAppCredential from "../../_utils/oauth/createOAuthAppCredential"; -import { decodeOAuthState } from "../../_utils/oauth/decodeOAuthState"; let client_id = ""; let client_secret = ""; diff --git a/packages/app-store/zohocrm/lib/CalendarService.ts b/packages/app-store/zohocrm/lib/CalendarService.ts index ef5bb4ec82..e5a7e8fcf0 100644 --- a/packages/app-store/zohocrm/lib/CalendarService.ts +++ b/packages/app-store/zohocrm/lib/CalendarService.ts @@ -16,7 +16,6 @@ import type { import type { CredentialPayload } from "@calcom/types/Credential"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; -import refreshOAuthTokens from "../../_utils/oauth/refreshOAuthTokens"; export type ZohoToken = { scope: string; @@ -201,19 +200,14 @@ export default class ZohoCrmCalendarService implements Calendar { client_secret: this.client_secret, refresh_token: credentialKey.refresh_token, }; - const zohoCrmTokenInfo = await refreshOAuthTokens( - async () => - await axios({ - method: "post", - url: url, - data: qs.stringify(formData), - headers: { - "Content-Type": "application/x-www-form-urlencoded;charset=utf-8", - }, - }), - "zohocrm", - credential.userId - ); + const zohoCrmTokenInfo = await axios({ + method: "post", + url: url, + data: qs.stringify(formData), + headers: { + "Content-Type": "application/x-www-form-urlencoded;charset=utf-8", + }, + }); if (!zohoCrmTokenInfo.data.error) { // set expiry date as offset from current time. zohoCrmTokenInfo.data.expiryDate = Math.round(Date.now() + 60 * 60); diff --git a/packages/app-store/zoomvideo/api/add.ts b/packages/app-store/zoomvideo/api/add.ts index 17c8797928..5083556051 100644 --- a/packages/app-store/zoomvideo/api/add.ts +++ b/packages/app-store/zoomvideo/api/add.ts @@ -5,7 +5,7 @@ import { WEBAPP_URL } from "@calcom/lib/constants"; import { defaultHandler, defaultResponder } from "@calcom/lib/server"; import prisma from "@calcom/prisma"; -import { encodeOAuthState } from "../../_utils/oauth/encodeOAuthState"; +import { encodeOAuthState } from "../../_utils/encodeOAuthState"; import { getZoomAppKeys } from "../lib"; async function handler(req: NextApiRequest) { diff --git a/packages/app-store/zoomvideo/api/callback.ts b/packages/app-store/zoomvideo/api/callback.ts index 7c9c20d60a..b97bab0f02 100644 --- a/packages/app-store/zoomvideo/api/callback.ts +++ b/packages/app-store/zoomvideo/api/callback.ts @@ -3,8 +3,8 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { WEBAPP_URL } from "@calcom/lib/constants"; import prisma from "@calcom/prisma"; +import createOAuthAppCredential from "../../_utils/createOAuthAppCredential"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; -import createOAuthAppCredential from "../../_utils/oauth/createOAuthAppCredential"; import { getZoomAppKeys } from "../lib"; export default async function handler(req: NextApiRequest, res: NextApiResponse) { diff --git a/packages/app-store/zoomvideo/lib/VideoApiAdapter.ts b/packages/app-store/zoomvideo/lib/VideoApiAdapter.ts index 20e8f900eb..ef227596c7 100644 --- a/packages/app-store/zoomvideo/lib/VideoApiAdapter.ts +++ b/packages/app-store/zoomvideo/lib/VideoApiAdapter.ts @@ -9,8 +9,6 @@ import type { CredentialPayload } from "@calcom/types/Credential"; import type { PartialReference } from "@calcom/types/EventManager"; import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter"; -import parseRefreshTokenResponse from "../../_utils/oauth/parseRefreshTokenResponse"; -import refreshOAuthTokens from "../../_utils/oauth/refreshOAuthTokens"; import { getZoomAppKeys } from "./getZoomAppKeys"; /** @link https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingcreate */ @@ -76,22 +74,17 @@ const zoomAuth = (credential: CredentialPayload) => { const { client_id, client_secret } = await getZoomAppKeys(); const authHeader = "Basic " + Buffer.from(client_id + ":" + client_secret).toString("base64"); - const response = await refreshOAuthTokens( - async () => - await fetch("https://zoom.us/oauth/token", { - method: "POST", - headers: { - Authorization: authHeader, - "Content-Type": "application/x-www-form-urlencoded", - }, - body: new URLSearchParams({ - refresh_token: refreshToken, - grant_type: "refresh_token", - }), - }), - "zoomvideo", - credential.userId - ); + const response = await fetch("https://zoom.us/oauth/token", { + method: "POST", + headers: { + Authorization: authHeader, + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ + refresh_token: refreshToken, + grant_type: "refresh_token", + }), + }); const responseBody = await handleZoomResponse(response, credential.id); @@ -101,7 +94,7 @@ const zoomAuth = (credential: CredentialPayload) => { } } // We check the if the new credentials matches the expected response structure - const parsedToken = parseRefreshTokenResponse(responseBody, zoomRefreshedTokenSchema); + const parsedToken = zoomRefreshedTokenSchema.safeParse(responseBody); // TODO: If the new token is invalid, initiate the fallback sequence instead of throwing // Expanding on this we can use server-to-server app and create meeting from admin calcom account diff --git a/packages/lib/constants.ts b/packages/lib/constants.ts index 02f532866a..6d302d7d03 100644 --- a/packages/lib/constants.ts +++ b/packages/lib/constants.ts @@ -99,6 +99,3 @@ export const ORGANIZATION_MIN_SEATS = 30; // Needed for emails in E2E export const IS_MAILHOG_ENABLED = process.env.E2E_TEST_MAILHOG_ENABLED === "1"; export const CALCOM_VERSION = process.env.NEXT_PUBLIC_CALCOM_VERSION as string; - -export const APP_CREDENTIAL_SHARING_ENABLED = - process.env.CALCOM_WEBHOOK_SECRET && process.env.CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY; diff --git a/turbo.json b/turbo.json index c6e6e41cbb..3a6325f844 100644 --- a/turbo.json +++ b/turbo.json @@ -197,14 +197,10 @@ "BASECAMP3_USER_AGENT", "AUTH_BEARER_TOKEN_VERCEL", "BUILD_ID", - "CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY", - "CALCOM_CREDENTIAL_SYNC_ENDPOINT", "CALCOM_ENV", "CALCOM_LICENSE_KEY", "CALCOM_TELEMETRY_DISABLED", - "CALCOM_WEBHOOK_HEADER_NAME", "CALENDSO_ENCRYPTION_KEY", - "CALCOM_WEBHOOK_SECRET", "CI", "CLOSECOM_API_KEY", "CRON_API_KEY",