Merge pull request #133 from calcom/fix/api-feedback
fix: address issues raised in preview PR
This commit is contained in:
@@ -179,15 +179,13 @@ We make sure of this by not using next in dev, but next build && next start, if
|
||||
See <https://github.com/vercel/next.js/blob/canary/packages/next/server/dev/hot-reloader.ts#L79>. 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.
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+28
-28
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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" });
|
||||
|
||||
@@ -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" });
|
||||
|
||||
@@ -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" });
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user