From a2d16800aafea29ce3e5c3b2ce4a88fb3350cea1 Mon Sep 17 00:00:00 2001 From: Agusti Fernandez Pardo Date: Sat, 30 Apr 2022 23:07:21 +0200 Subject: [PATCH] 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*`, + }, + ], + }; }, });