diff --git a/README.md b/README.md index 009ac0dd0d..c806ecc3e4 100644 --- a/README.md +++ b/README.md @@ -179,15 +179,13 @@ We make sure of this by not using next in dev, but next build && next start, if See . Here in dev mode OPTIONS method is hardcoded to return only GET and OPTIONS as allowed method. Running in Production mode would cause this file to be not used. This is hot-reloading logic only. To remove this limitation, we need to ensure that on local endpoints are requested by swagger at /api/v1 and not /v1 - ## Hosted api through cal.com -Go to console.com +Go to console.cal.com Add a deployment or go to an existing one. Activate API or Admin addon -Provide your DATABASE-URL -Store it safely, you'll get a customApiID, save it. -call api.cal.com?apiKey=your_cal_instance_apiKey&customApiId=cal_datasource_key +Provide your `DATABASE_URL` +Now you can call api.cal.com?key=CALCOM_LICENSE_KEY, which will connect to your own databaseUrl. ## How to deploy We recommend deploying API in vercel. diff --git a/lib/constants.ts b/lib/constants.ts index 52c7fe0a41..fb04db4a1c 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -1 +1 @@ -export const PRISMA_CLIENT_CACHING_TIME = 1000 * 60 * 60 * 24; +export const PRISMA_CLIENT_CACHING_TIME = 1000 * 60 * 60 * 24; // one day in ms diff --git a/lib/helpers/customPrisma.ts b/lib/helpers/customPrisma.ts index d5aeaf9749..f2d038c5e5 100644 --- a/lib/helpers/customPrisma.ts +++ b/lib/helpers/customPrisma.ts @@ -3,7 +3,6 @@ import cache from "memory-cache"; import { NextMiddleware } from "next-api-middleware"; import { PRISMA_CLIENT_CACHING_TIME } from "@calcom/api/lib/constants"; -// import prismaAdmin from "@calcom/console/modules/common/utils/prisma"; import { CONSOLE_URL } from "@calcom/lib/constants"; import { prisma, customPrisma } from "@calcom/prisma"; @@ -16,38 +15,39 @@ export const customPrismaClient: NextMiddleware = async (req, res, next) => { if (!key) { req.prisma = prisma; await next(); - } else { - // If we have a key, we check if the deployment matching the key, has a databaseUrl value set. - const databaseUrl = await fetch( - `${process.env.NEXT_PUBLIC_CONSOLE_URL || CONSOLE_URL}/api/deployments/database?key=${key}` - ) - .then((res) => res.json()) - .then((res) => res.databaseUrl); + return; + } + // If we have a key, we check if the deployment matching the key, has a databaseUrl value set. + const databaseUrl = await fetch( + `${process.env.NEXT_PUBLIC_CONSOLE_URL || CONSOLE_URL}/api/deployments/database?key=${key}` + ) + .then((res) => res.json()) + .then((res) => res.databaseUrl); - if (!databaseUrl) { - res.status(400).json({ error: "no databaseUrl set up at your instance yet" }); - return; - } - // FIXME: Add some checks for the databaseUrl to make sure it is valid. (e.g. not a localhost) - const hashedUrl = await hash(databaseUrl, 12); + if (!databaseUrl) { + res.status(400).json({ error: "no databaseUrl set up at your instance yet" }); + return; + } + // FIXME: Add some checks for the databaseUrl to make sure it is valid. (e.g. not a localhost) + const hashedUrl = await hash(databaseUrl, 12); - const cachedPrisma = cache.get(hashedUrl); - /* We cache each cusotm prisma client for 24h to avoid too many requests to the database. */ - if (!cachedPrisma) { - cache.put( - hashedUrl, - customPrisma({ datasources: { db: { url: databaseUrl } } }), - PRISMA_CLIENT_CACHING_TIME // Cache the prisma client for 24 hours - ); - } - req.prisma = customPrisma({ datasources: { db: { url: databaseUrl } } }); - /* @note: + const cachedPrisma = cache.get(hashedUrl); + /* We cache each cusotm prisma client for 24h to avoid too many requests to the database. */ + if (!cachedPrisma) { + cache.put( + hashedUrl, + customPrisma({ datasources: { db: { url: databaseUrl } } }), + PRISMA_CLIENT_CACHING_TIME // Cache the prisma client for 24 hours + ); + } + req.prisma = customPrisma({ datasources: { db: { url: databaseUrl } } }); + /* @note: In order to skip verifyApiKey for customPrisma requests, we pass isAdmin true, and userId 0, if we detect them later, we skip verifyApiKey logic and pass onto next middleware instead. */ - req.isAdmin = true; - req.userId = 0; - } + req.isAdmin = true; + req.userId = 0; + await next(); }; diff --git a/lib/helpers/verifyApiKey.ts b/lib/helpers/verifyApiKey.ts index 4a375aee26..e5cf0332a3 100644 --- a/lib/helpers/verifyApiKey.ts +++ b/lib/helpers/verifyApiKey.ts @@ -37,7 +37,7 @@ export const verifyApiKey: NextMiddleware = async (req, res, next) => { // save the user id in the request for later use req.userId = apiKey.userId; // save the isAdmin boolean here for later use - req.isAdmin = await isAdminGuard(req.userId, prisma); + req.isAdmin = await isAdminGuard(req); await next(); }; diff --git a/lib/utils/isAdmin.ts b/lib/utils/isAdmin.ts index aab1454b62..c07086fbd6 100644 --- a/lib/utils/isAdmin.ts +++ b/lib/utils/isAdmin.ts @@ -1,6 +1,8 @@ -import { PrismaClient, UserPermissionRole } from "@prisma/client"; +import { UserPermissionRole } from "@prisma/client"; +import type { NextApiRequest } from "next/types"; -export const isAdminGuard = async (userId: number, prisma: PrismaClient) => { +export const isAdminGuard = async (req: NextApiRequest) => { + const { userId, prisma } = req; const user = await prisma.user.findUnique({ where: { id: userId } }); return user?.role === UserPermissionRole.ADMIN; }; diff --git a/lib/utils/webhookSubscriptions.ts b/lib/utils/webhookSubscriptions.ts index af185248b3..51de834f2d 100644 --- a/lib/utils/webhookSubscriptions.ts +++ b/lib/utils/webhookSubscriptions.ts @@ -5,7 +5,7 @@ export type GetSubscriberOptions = { eventTypeId: number; triggerEvent: WebhookTriggerEvents; }; -/** @note will this not work with custom prisma? since we're importing prisma directly and not passing it from request here **/ + const getWebhooks = async (options: GetSubscriberOptions, prisma: PrismaClient) => { const { userId, eventTypeId } = options; const allWebhooks = await prisma.webhook.findMany({ diff --git a/pages/api/users/[userId]/_delete.ts b/pages/api/users/[userId]/_delete.ts index 0f5c208e2a..391e341244 100644 --- a/pages/api/users/[userId]/_delete.ts +++ b/pages/api/users/[userId]/_delete.ts @@ -3,7 +3,6 @@ import type { NextApiRequest } from "next"; import { HttpError } from "@calcom/lib/http-error"; import { defaultResponder } from "@calcom/lib/server"; -import { isAdminGuard } from "@lib/utils/isAdmin"; import { schemaQueryUserId } from "@lib/validations/shared/queryUserId"; /** @@ -31,9 +30,8 @@ import { schemaQueryUserId } from "@lib/validations/shared/queryUserId"; * description: Authorization information is missing or invalid. */ export async function deleteHandler(req: NextApiRequest) { - const { prisma } = req; + const { prisma, isAdmin } = req; const query = schemaQueryUserId.parse(req.query); - const isAdmin = await isAdminGuard(req.userId, req.prisma); // Here we only check for ownership of the user if the user is not admin, otherwise we let ADMIN's edit any user if (!isAdmin && query.userId !== req.userId) throw new HttpError({ statusCode: 401, message: "Unauthorized" }); diff --git a/pages/api/users/[userId]/_get.ts b/pages/api/users/[userId]/_get.ts index 76b2603a89..e379c5fbf1 100644 --- a/pages/api/users/[userId]/_get.ts +++ b/pages/api/users/[userId]/_get.ts @@ -3,7 +3,6 @@ import type { NextApiRequest } from "next"; import { HttpError } from "@calcom/lib/http-error"; import { defaultResponder } from "@calcom/lib/server"; -import { isAdminGuard } from "@lib/utils/isAdmin"; import { schemaQueryUserId } from "@lib/validations/shared/queryUserId"; import { schemaUserReadPublic } from "@lib/validations/user"; @@ -32,10 +31,9 @@ import { schemaUserReadPublic } from "@lib/validations/user"; * description: User was not found */ export async function getHandler(req: NextApiRequest) { - const { prisma } = req; + const { prisma, isAdmin } = req; const query = schemaQueryUserId.parse(req.query); - const isAdmin = await isAdminGuard(req.userId, req.prisma); // Here we only check for ownership of the user if the user is not admin, otherwise we let ADMIN's edit any user if (!isAdmin && query.userId !== req.userId) throw new HttpError({ statusCode: 401, message: "Unauthorized" }); diff --git a/pages/api/users/[userId]/_patch.ts b/pages/api/users/[userId]/_patch.ts index b7bf27c47f..9e6fee7f91 100644 --- a/pages/api/users/[userId]/_patch.ts +++ b/pages/api/users/[userId]/_patch.ts @@ -3,7 +3,6 @@ import type { NextApiRequest } from "next"; import { HttpError } from "@calcom/lib/http-error"; import { defaultResponder } from "@calcom/lib/server"; -import { isAdminGuard } from "@lib/utils/isAdmin"; import { schemaQueryUserId } from "@lib/validations/shared/queryUserId"; import { schemaUserEditBodyParams, schemaUserReadPublic } from "@lib/validations/user"; @@ -53,9 +52,8 @@ import { schemaUserEditBodyParams, schemaUserReadPublic } from "@lib/validations * description: Authorization information is missing or invalid. */ export async function patchHandler(req: NextApiRequest) { - const { prisma } = req; + const { prisma, isAdmin } = req; const query = schemaQueryUserId.parse(req.query); - const isAdmin = await isAdminGuard(req.userId, req.prisma); // Here we only check for ownership of the user if the user is not admin, otherwise we let ADMIN's edit any user if (!isAdmin && query.userId !== req.userId) throw new HttpError({ statusCode: 401, message: "Unauthorized" }); diff --git a/pages/api/users/_get.ts b/pages/api/users/_get.ts index 42ec41ecfb..67bb53e2d1 100644 --- a/pages/api/users/_get.ts +++ b/pages/api/users/_get.ts @@ -2,7 +2,6 @@ import type { NextApiRequest } from "next"; import { defaultResponder } from "@calcom/lib/server"; -import { isAdminGuard } from "@lib/utils/isAdmin"; import { schemaUsersReadPublic } from "@lib/validations/user"; import { Prisma } from ".prisma/client"; @@ -23,8 +22,8 @@ import { Prisma } from ".prisma/client"; * 404: * description: No users were found */ -async function getHandler({ userId, prisma }: NextApiRequest) { - const isAdmin = await isAdminGuard(userId, prisma); +async function getHandler(req: NextApiRequest) { + const { userId, prisma, isAdmin } = req; const where: Prisma.UserWhereInput = {}; // If user is not ADMIN, return only his data. if (!isAdmin) where.id = userId; diff --git a/pages/api/users/_post.ts b/pages/api/users/_post.ts index 8809aa7e8a..03ab696e96 100644 --- a/pages/api/users/_post.ts +++ b/pages/api/users/_post.ts @@ -3,12 +3,10 @@ import type { NextApiRequest } from "next"; import { HttpError } from "@calcom/lib/http-error"; import { defaultResponder } from "@calcom/lib/server"; -import { isAdminGuard } from "@lib/utils/isAdmin"; import { schemaUserCreateBodyParams } from "@lib/validations/user"; async function postHandler(req: NextApiRequest) { - const { prisma } = req; - const isAdmin = await isAdminGuard(req.userId, req.prisma); + const { prisma, isAdmin } = req; // If user is not ADMIN, return unauthorized. if (!isAdmin) throw new HttpError({ statusCode: 401, message: "You are not authorized" }); const data = schemaUserCreateBodyParams.parse(req.body);