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/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..5e40353edf 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", @@ -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. - { - 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*`, + }, + ], + }; }, }); 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 .",