Merge pull request #133 from calcom/fix/api-feedback

fix: address issues raised in preview PR
This commit is contained in:
Agusti Fernandez Pardo
2022-06-24 01:17:28 +02:00
committed by GitHub
11 changed files with 44 additions and 53 deletions
+3 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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();
};
+1 -1
View File
@@ -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();
};
+4 -2
View File
@@ -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;
};
+1 -1
View File
@@ -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({
+1 -3
View File
@@ -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" });
+1 -3
View File
@@ -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" });
+1 -3
View File
@@ -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 -3
View File
@@ -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;
+1 -3
View File
@@ -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);