From a2d16800aafea29ce3e5c3b2ce4a88fb3350cea1 Mon Sep 17 00:00:00 2001 From: Agusti Fernandez Pardo Date: Sat, 30 Apr 2022 23:07:21 +0200 Subject: [PATCH 1/4] Fixes: with hariom help for running api in prod for swagger even in dev --- lib/helpers/addRequestid.ts | 15 ++++++++++ lib/helpers/httpMethods.ts | 2 +- lib/helpers/verifyApiKey.ts | 18 ++++++------ next.config.js | 56 +++++++++++++++++-------------------- 4 files changed, 50 insertions(+), 41 deletions(-) diff --git a/lib/helpers/addRequestid.ts b/lib/helpers/addRequestid.ts index 263c7aacc1..6bdf1e546a 100644 --- a/lib/helpers/addRequestid.ts +++ b/lib/helpers/addRequestid.ts @@ -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(); }; diff --git a/lib/helpers/httpMethods.ts b/lib/helpers/httpMethods.ts index 0617208a90..0bf6243e0f 100644 --- a/lib/helpers/httpMethods.ts +++ b/lib/helpers/httpMethods.ts @@ -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` }); diff --git a/lib/helpers/verifyApiKey.ts b/lib/helpers/verifyApiKey.ts index c4fabac652..cf5c4a8472 100644 --- a/lib/helpers/verifyApiKey.ts +++ b/lib/helpers/verifyApiKey.ts @@ -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(); }; diff --git a/next.config.js b/next.config.js index 1542c7f0ff..6122951e9a 100644 --- a/next.config.js +++ b/next.config.js @@ -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", @@ -5,37 +7,31 @@ const withTM = require("next-transpile-modules")([ "@calcom/ee", ]); +// use something like withPlugins([withTM], {}) if more plugins added later. + 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. - { - source: "/:rest*", - destination: "/api/:rest*", - }, - // @note: redirects requests from api/v*/:rest to /api/:rest?version=* passing version as a query parameter. - { - source: "/api/v:version/:rest*", - destination: "/api/:rest*?version=:version", - }, - ]; + return { + beforeFiles: [ + // This redirects requests recieved at / the root to the /api/ folder. + { + source: "/v:version/:rest*", + destination: "/api/v:version/:rest*", + }, + // 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*`, + }, + ], + }; }, }); From 886b101887b1be6f17d746e270ed33e4c9da9edb Mon Sep 17 00:00:00 2001 From: Agusti Fernandez Pardo Date: Sat, 30 Apr 2022 23:57:57 +0200 Subject: [PATCH 2/4] fix: makes patch work by working around and faking production for yarn dev in api --- next.config.js | 52 +++++++++++++++++++++++++++++--------------------- package.json | 4 ++-- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/next.config.js b/next.config.js index 6122951e9a..fa5bc84e10 100644 --- a/next.config.js +++ b/next.config.js @@ -10,28 +10,36 @@ const withTM = require("next-transpile-modules")([ // use something like withPlugins([withTM], {}) if more plugins added later. module.exports = withTM({ + async headers() { + return [ + { + // matching all API routes + 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 { - beforeFiles: [ - // This redirects requests recieved at / the root to the /api/ folder. - { - source: "/v:version/:rest*", - destination: "/api/v:version/:rest*", - }, - // 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*`, - }, - ], - }; + return [ + // This redirects requests recieved at / the root to the /api/ folder. + { + source: "/:rest*", + destination: "/api/:rest*", + }, + // This redirects requests to api/v*/ to /api/ passing version as a query parameter. + { + source: "/api/v:version/:rest*", + destination: "/api/:rest*?version=:version", + }, + ]; }, }); diff --git a/package.json b/package.json index 42f655236a..18d9081665 100644 --- a/package.json +++ b/package.json @@ -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 .", From 8aa4852b875d37ec1dcf0a1b7dc8099bfe4163ff Mon Sep 17 00:00:00 2001 From: Agusti Fernandez Pardo Date: Sun, 1 May 2022 00:33:05 +0200 Subject: [PATCH 3/4] remove headers from next config, add rewrites from hariom pr --- next.config.js | 52 +++++++++++++++++++++----------------------------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/next.config.js b/next.config.js index fa5bc84e10..6122951e9a 100644 --- a/next.config.js +++ b/next.config.js @@ -10,36 +10,28 @@ const withTM = require("next-transpile-modules")([ // use something like withPlugins([withTM], {}) if more plugins added later. module.exports = withTM({ - async headers() { - return [ - { - // matching all API routes - 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 [ - // This redirects requests recieved at / the root to the /api/ folder. - { - source: "/:rest*", - destination: "/api/:rest*", - }, - // This redirects requests to api/v*/ to /api/ passing version as a query parameter. - { - source: "/api/v:version/:rest*", - destination: "/api/:rest*?version=:version", - }, - ]; + return { + beforeFiles: [ + // This redirects requests recieved at / the root to the /api/ folder. + { + source: "/v:version/:rest*", + destination: "/api/v:version/:rest*", + }, + // 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*`, + }, + ], + }; }, }); From a41fc5732e8a837351c2917808520f4e0671f7da Mon Sep 17 00:00:00 2001 From: Agusti Fernandez Pardo Date: Sun, 1 May 2022 00:36:59 +0200 Subject: [PATCH 4/4] fix: readme --- README.md | 6 ++++++ next.config.js | 2 -- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4bba94e69a..d210672e53 100644 --- a/README.md +++ b/README.md @@ -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 . 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 diff --git a/next.config.js b/next.config.js index 6122951e9a..5e40353edf 100644 --- a/next.config.js +++ b/next.config.js @@ -7,8 +7,6 @@ const withTM = require("next-transpile-modules")([ "@calcom/ee", ]); -// use something like withPlugins([withTM], {}) if more plugins added later. - module.exports = withTM({ async rewrites() { return {