Merge pull request #65 from calcom/fix/api-hariom
commit
6124577bc2
|
@ -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.
|
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
|
> 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
|
||||||
|
|
|
@ -4,6 +4,21 @@ import { NextMiddleware } from "next-api-middleware";
|
||||||
export const addRequestId: NextMiddleware = async (_req, res, next) => {
|
export const addRequestId: NextMiddleware = async (_req, res, next) => {
|
||||||
// Apply header with unique ID to every request
|
// Apply header with unique ID to every request
|
||||||
res.setHeader("Calcom-Response-ID", nanoid());
|
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
|
// Let remaining middleware and API route execute
|
||||||
await next();
|
await next();
|
||||||
};
|
};
|
||||||
|
|
|
@ -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.
|
// 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 => {
|
export const httpMethods = (allowedHttpMethod: string[]): NextMiddleware => {
|
||||||
return async function (req, res, next) {
|
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();
|
await next();
|
||||||
} else {
|
} else {
|
||||||
res.status(405).json({ message: `Only ${allowedHttpMethod} Method allowed` });
|
res.status(405).json({ message: `Only ${allowedHttpMethod} Method allowed` });
|
||||||
|
|
|
@ -8,8 +8,6 @@ import prisma from "@calcom/prisma";
|
||||||
declare module "next" {
|
declare module "next" {
|
||||||
export interface NextApiRequest extends IncomingMessage {
|
export interface NextApiRequest extends IncomingMessage {
|
||||||
userId: number;
|
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.
|
// This verifies the apiKey and sets the user if it is valid.
|
||||||
export const verifyApiKey: NextMiddleware = async ({ query: { apiKey }, ...req }, res, next) => {
|
export const verifyApiKey: NextMiddleware = async (req, res, next) => {
|
||||||
if (!apiKey) return res.status(401).json({ message: "No apiKey provided" });
|
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_"
|
// 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.
|
// Hash the key again before matching against the database records.
|
||||||
const hashedKey = hashAPIKey(strippedApiKey);
|
const hashedKey = hashAPIKey(strippedApiKey);
|
||||||
// Check if the hashed api key exists in database.
|
// 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 we cannot find any api key. Throw a 401 Unauthorized.
|
||||||
if (!validApiKey) return res.status(401).json({ error: "Your apiKey is not valid" });
|
if (!apiKey) return res.status(401).json({ error: "Your apiKey is not valid" });
|
||||||
if (validApiKey.expiresAt && dateNotInPast(validApiKey.expiresAt)) {
|
if (apiKey.expiresAt && dateNotInPast(apiKey.expiresAt)) {
|
||||||
return res.status(401).json({ error: "This apiKey is expired" });
|
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 */
|
/* We save the user id in the request for later use */
|
||||||
req.userId = validApiKey.userId;
|
req.userId = apiKey.userId;
|
||||||
await next();
|
await next();
|
||||||
};
|
};
|
||||||
|
|
|
@ -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")([
|
const withTM = require("next-transpile-modules")([
|
||||||
"@calcom/app-store",
|
"@calcom/app-store",
|
||||||
"@calcom/prisma",
|
"@calcom/prisma",
|
||||||
|
@ -6,36 +8,28 @@ const withTM = require("next-transpile-modules")([
|
||||||
]);
|
]);
|
||||||
|
|
||||||
module.exports = withTM({
|
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() {
|
async rewrites() {
|
||||||
return [
|
return {
|
||||||
// @note: redirects requests from: "/:rest*" the root level to the "/api/:rest*" folder by default.
|
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",
|
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*`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
"author": "Cal.com Inc.",
|
"author": "Cal.com Inc.",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "PORT=3002 next",
|
"dev": "next build && PORT=3002 next start",
|
||||||
"start": "next start",
|
"start": "PORT=3002 next start",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"lint-fix": "next lint --fix && prettier --write .",
|
"lint-fix": "next lint --fix && prettier --write .",
|
||||||
|
|
Loading…
Reference in New Issue