Merge pull request #65 from calcom/fix/api-hariom

pull/9078/head
Agusti Fernandez Pardo 2022-05-01 00:41:30 +02:00 committed by GitHub
commit 6124577bc2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 56 additions and 43 deletions

View File

@ -172,3 +172,9 @@ DATABASE_URL=DATABASE_URL="postgresql://postgres:@localhost:5450/calendso"
API*KEY_PREFIX=cal*# This can be changed per envirorment so cal*test* for staging for example.
> If you're self-hosting under our commercial license, you can use any prefix you want for api keys. either leave the default cal\_ (not providing any envirorment variable) or modify it
**Ensure that while testing swagger, API project should be run in production mode**
We make sure of this by not using next in dev, but next build && next start, if you want hot module reloading and such when developing, please use yarn run next directly on apps/api.
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

View File

@ -4,6 +4,21 @@ import { NextMiddleware } from "next-api-middleware";
export const addRequestId: NextMiddleware = async (_req, res, next) => {
// Apply header with unique ID to every request
res.setHeader("Calcom-Response-ID", nanoid());
// Add all headers here instead of next.config.js as it is throwing error( Cannot set headers after they are sent to the client) for OPTIONS method
// It is known to happen only in Dev Mode.
res.setHeader("Access-Control-Allow-Credentials", "true");
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS, PATCH, DELETE, POST, PUT");
res.setHeader(
"Access-Control-Allow-Headers",
"X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, Content-Type, api_key, Authorization"
);
// Ensure all OPTIONS request are automatically successful. Headers are already set above.
if (_req.method === "OPTIONS") {
res.status(200).end();
return;
}
// Let remaining middleware and API route execute
await next();
};

View File

@ -15,7 +15,7 @@ export const httpMethod = (allowedHttpMethod: "GET" | "POST" | "PATCH" | "DELETE
// that checks if it's just a string or an array and apply the correct logic to both cases.
export const httpMethods = (allowedHttpMethod: string[]): NextMiddleware => {
return async function (req, res, next) {
if (allowedHttpMethod.map((method) => method === req.method)) {
if (allowedHttpMethod.some((method) => method === req.method || req.method == "OPTIONS")) {
await next();
} else {
res.status(405).json({ message: `Only ${allowedHttpMethod} Method allowed` });

View File

@ -8,8 +8,6 @@ import prisma from "@calcom/prisma";
declare module "next" {
export interface NextApiRequest extends IncomingMessage {
userId: number;
body: any;
query: { [key: string]: string | string[] };
}
}
@ -22,21 +20,21 @@ export const dateNotInPast = function (date: Date) {
};
// This verifies the apiKey and sets the user if it is valid.
export const verifyApiKey: NextMiddleware = async ({ query: { apiKey }, ...req }, res, next) => {
if (!apiKey) return res.status(401).json({ message: "No apiKey provided" });
export const verifyApiKey: NextMiddleware = async (req, res, next) => {
if (!req.query.apiKey) return res.status(401).json({ message: "No apiKey provided" });
// We remove the prefix from the user provided api_key. If no env set default to "cal_"
const strippedApiKey = `${apiKey}`.replace(process.env.API_KEY_PREFIX || " cal_", "");
const strippedApiKey = `${req.query.apiKey}`.replace(process.env.API_KEY_PREFIX || "cal_", "");
// Hash the key again before matching against the database records.
const hashedKey = hashAPIKey(strippedApiKey);
// Check if the hashed api key exists in database.
const validApiKey = await prisma.apiKey.findUnique({ where: { hashedKey } });
const apiKey = await prisma.apiKey.findUnique({ where: { hashedKey } });
// If we cannot find any api key. Throw a 401 Unauthorized.
if (!validApiKey) return res.status(401).json({ error: "Your apiKey is not valid" });
if (validApiKey.expiresAt && dateNotInPast(validApiKey.expiresAt)) {
if (!apiKey) return res.status(401).json({ error: "Your apiKey is not valid" });
if (apiKey.expiresAt && dateNotInPast(apiKey.expiresAt)) {
return res.status(401).json({ error: "This apiKey is expired" });
}
if (!validApiKey.userId) return res.status(404).json({ error: "No user found for this apiKey" });
if (!apiKey.userId) return res.status(404).json({ error: "No user found for this apiKey" });
/* We save the user id in the request for later use */
req.userId = validApiKey.userId;
req.userId = apiKey.userId;
await next();
};

View File

@ -1,3 +1,5 @@
// https://www.npmjs.com/package/next-transpile-modules
// This makes our @calcom/prisma package from the monorepo to be transpiled and usable by API
const withTM = require("next-transpile-modules")([
"@calcom/app-store",
"@calcom/prisma",
@ -6,36 +8,28 @@ const withTM = require("next-transpile-modules")([
]);
module.exports = withTM({
async headers() {
return [
{
// @note: disabling CORS matching all API routes as this will be a our Public API
source: "/api/:path*",
headers: [
{ key: "Access-Control-Allow-Credentials", value: "true" },
{ key: "Access-Control-Allow-Origin", value: "*" },
{ key: "Access-Control-Allow-Methods", value: "GET,OPTIONS,PATCH,DELETE,POST,PUT" },
{
key: "Access-Control-Allow-Headers",
value:
"X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, Content-Type, api_key, Authorization",
},
],
},
];
},
async rewrites() {
return [
// @note: redirects requests from: "/:rest*" the root level to the "/api/:rest*" folder by default.
return {
beforeFiles: [
// This redirects requests recieved at / the root to the /api/ folder.
{
source: "/:rest*",
destination: "/api/:rest*",
source: "/v:version/:rest*",
destination: "/api/v:version/:rest*",
},
// @note: redirects requests from api/v*/:rest to /api/:rest?version=* passing version as a query parameter.
// This redirects requests to api/v*/ to /api/ passing version as a query parameter.
{
source: "/api/v:version/:rest*",
destination: "/api/:rest*?version=:version",
},
];
],
fallback: [
// These rewrites are checked after both pages/public files
// and dynamic routes are checked
{
source: "/:path*",
destination: `/api/:path*`,
},
],
};
},
});

View File

@ -7,8 +7,8 @@
"author": "Cal.com Inc.",
"private": true,
"scripts": {
"dev": "PORT=3002 next",
"start": "next start",
"dev": "next build && PORT=3002 next start",
"start": "PORT=3002 next start",
"build": "next build",
"lint": "next lint",
"lint-fix": "next lint --fix && prettier --write .",