From ec8fe3693006405d156448cfdd472d03c2b797f7 Mon Sep 17 00:00:00 2001 From: Agusti Fernandez Pardo Date: Tue, 29 Mar 2022 02:25:24 +0200 Subject: [PATCH] feat: successfully protected all endpoints with next-api-middleware for verifyApiKey --- lib/helpers/addRequestid.ts | 9 +++ lib/helpers/captureError.ts | 47 -------------- lib/helpers/captureErrors.ts | 16 +++++ lib/helpers/verifyApiKey.ts | 52 ++++++---------- lib/helpers/withMiddleware.ts | 15 +++++ package.json | 1 + pages/api/users/index.ts | 4 +- yarn-error.log | 112 +++++++++++++++++++++++++++++++--- 8 files changed, 167 insertions(+), 89 deletions(-) create mode 100644 lib/helpers/addRequestid.ts delete mode 100644 lib/helpers/captureError.ts create mode 100644 lib/helpers/captureErrors.ts create mode 100644 lib/helpers/withMiddleware.ts diff --git a/lib/helpers/addRequestid.ts b/lib/helpers/addRequestid.ts new file mode 100644 index 0000000000..2096927a1e --- /dev/null +++ b/lib/helpers/addRequestid.ts @@ -0,0 +1,9 @@ +import { NextMiddleware } from "next-api-middleware"; +import { nanoid } from "nanoid"; + +export const addRequestId: NextMiddleware = async (_req, res, next) => { + // Apply header + res.setHeader("X-Response-ID", nanoid()); + // Let remaining middleware and API route execute + await next(); +}; \ No newline at end of file diff --git a/lib/helpers/captureError.ts b/lib/helpers/captureError.ts deleted file mode 100644 index 8835a3cb91..0000000000 --- a/lib/helpers/captureError.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { label, NextMiddleware } from "next-api-middleware"; -import * as Sentry from "@sentry/nextjs"; -import nanoid from "nanoid"; - -// 1 – Create middleware functions - -const captureErrors: NextMiddleware = async (req, res, next) => { - try { - // Catch any errors that are thrown in remaining - // middleware and the API route handler - await next(); - } catch (err) { - const eventId = Sentry.captureException(err); - - res.status(500); - res.json({ error: err }); - } -}; - -const addRequestId: NextMiddleware = async (req, res, next) => { - // Let remaining middleware and API route execute - await next(); - - // Apply header - res.setHeader("X-Response-ID", nanoid()); -}; - -// 2 – Use `label` to assemble all middleware - -const withMiddleware = label( - { - addRequestId, - sentry: captureErrors, // <-- Optionally alias middleware - }, - ["sentry"] // <-- Provide a list of middleware to call automatically -); - -// 3 – Define your API route handler - -const apiRouteHandler = async (req, res) => { - res.status(200); - res.send("Hello world!"); -}; - -// 4 – Choose middleware to invoke for this API route - -export default withMiddleware("addRequestId")(apiRouteHandler); \ No newline at end of file diff --git a/lib/helpers/captureErrors.ts b/lib/helpers/captureErrors.ts new file mode 100644 index 0000000000..b78174f36a --- /dev/null +++ b/lib/helpers/captureErrors.ts @@ -0,0 +1,16 @@ +import { NextMiddleware } from "next-api-middleware"; +import * as Sentry from "@sentry/nextjs"; + +export const captureErrors: NextMiddleware = async (_req, res, next) => { + try { + // Catch any errors that are thrown in remaining + // middleware and the API route handler + await next(); + } catch (err) { + const eventId = Sentry.captureException(err); + console.log(eventId) + res.status(500); + res.json({ error: err }); + } +}; + diff --git a/lib/helpers/verifyApiKey.ts b/lib/helpers/verifyApiKey.ts index 41dbb094d7..ffc602dcde 100644 --- a/lib/helpers/verifyApiKey.ts +++ b/lib/helpers/verifyApiKey.ts @@ -1,36 +1,20 @@ -// https://github.com/htunnicliff/next-api-middleware -// import prisma from "@calcom/prisma"; +import { NextMiddleware } from "next-api-middleware"; +// import { nanoid } from "nanoid"; +import prisma from "@calcom/prisma"; -// import { User } from "@calcom/prisma/client"; -// import type { NextApiRequest, NextApiResponse } from "next"; +const dateInPast = function (firstDate: Date, secondDate: Date) { + if (firstDate.setHours(0, 0, 0, 0) <= secondDate.setHours(0, 0, 0, 0)) { + return true; + } +} +const today = new Date(); -// type ResponseData = { -// data?: User[]; -// error?: unknown; -// }; -// const dateInPast = function (firstDate: Date, secondDate: Date) { -// if (firstDate.setHours(0, 0, 0, 0) <= secondDate.setHours(0, 0, 0, 0)) { -// return true; -// } - -// return false; -// }; -// const today = new Date(); - -// export default async function user(req: NextApiRequest, res: NextApiResponse) { -// const apiKey = req.query.apiKey as string; -// const apiInDb = await prisma.apiKey.findUnique({ where: { id: apiKey } }); -// if (!apiInDb) throw new Error('API key not found'); -// const { expiresAt } = apiInDb; -// // if (!apiInDb) res.status(400).json({ error: 'Your api key is not valid' }); -// if (expiresAt && dateInPast(expiresAt, today)) { -// try { -// const data = await prisma.user.findMany(); -// res.status(200).json({ data }); -// } catch (error) { -// // FIXME: Add zod for validation/error handling -// res.status(400).json({ error: error }); -// } -// } else res.status(400).json({ error: 'Your api key is not valid' }); - -// } +export const verifyApiKey: NextMiddleware = async (req, res, next) => { + const apiKey = await prisma.apiKey.findUnique({ where: { id: req.query.apiKey as string } }); + if (!apiKey) { + res.status(400).json({ error: 'Your api key is not valid' }); + throw new Error('No api key found'); + } + if (apiKey.expiresAt && dateInPast(apiKey.expiresAt, today)) await next(); + else res.status(400).json({ error: 'Your api key is not valid' }); +}; diff --git a/lib/helpers/withMiddleware.ts b/lib/helpers/withMiddleware.ts new file mode 100644 index 0000000000..99f3958af7 --- /dev/null +++ b/lib/helpers/withMiddleware.ts @@ -0,0 +1,15 @@ +import { label } from "next-api-middleware"; +import { addRequestId } from "./addRequestid"; +import { captureErrors } from "./captureErrors"; +import { verifyApiKey } from "./verifyApiKey"; + +const withMiddleware = label( + { + addRequestId, + verifyApiKey, + sentry: captureErrors, // <-- Optionally alias middleware + }, + ["sentry","verifyApiKey"] // <-- Provide a list of middleware to call automatically +); + +export default withMiddleware; \ No newline at end of file diff --git a/package.json b/package.json index e70d483ecd..feaee290bb 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "node-mocks-http": "^1.11.0" }, "dependencies": { + "@sentry/nextjs": "^6.19.2", "next": "^12.1.0", "next-api-middleware": "^1.0.1", "next-transpile-modules": "^9.0.0", diff --git a/pages/api/users/index.ts b/pages/api/users/index.ts index 8f83378ead..f431caa6c8 100644 --- a/pages/api/users/index.ts +++ b/pages/api/users/index.ts @@ -1,6 +1,7 @@ import prisma from "@calcom/prisma"; import { User } from "@calcom/prisma/client"; +import withMiddleware from "@lib/helpers/withMiddleware"; import type { NextApiRequest, NextApiResponse } from "next"; type ResponseData = { @@ -8,8 +9,9 @@ type ResponseData = { error?: unknown; }; -export default async function user(req: NextApiRequest, res: NextApiResponse) { +async function user(req: NextApiRequest, res: NextApiResponse) { const data = await prisma.user.findMany(); if (data) res.status(200).json({ data }); else res.status(400).json({ error: "No data found" }); } +export default withMiddleware("addRequestId")(user); \ No newline at end of file diff --git a/yarn-error.log b/yarn-error.log index e56325651f..3e1a5107cc 100644 --- a/yarn-error.log +++ b/yarn-error.log @@ -1,8 +1,8 @@ Arguments: - /Users/ag/Library/Application Support/fnm/node-versions/v14.17.6/installation/bin/node /Users/ag/Library/Caches/fnm_multishells/3034_1648146693474/bin/yarn add --dev -W @typescript-eslint + /Users/ag/Library/Application Support/fnm/node-versions/v14.17.6/installation/bin/node /Users/ag/Library/Caches/fnm_multishells/13639_1648391401808/bin/yarn add -W @types/next-api-middleware PATH: - /Users/ag/Library/pnpm:/Users/ag/Library/Caches/fnm_multishells/3034_1648146693474/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/ag/.local/bin:/Users/ag/.fig/bin:/Users/ag/.local/bin:/Users/ag/.local/bin:/Users/ag/.yarn/bin + /Users/ag/Library/pnpm:/Users/ag/Library/Caches/fnm_multishells/13639_1648391401808/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/ag/.local/bin:/Users/ag/.fig/bin:/Users/ag/.local/bin:/Users/ag/.local/bin:/Users/ag/.yarn/bin Yarn version: 1.22.17 @@ -14,8 +14,8 @@ Platform: darwin x64 Trace: - Error: https://registry.yarnpkg.com/@typescript-eslint: Request "https://registry.yarnpkg.com/@typescript-eslint" returned a 405 - at Request.params.callback [as _callback] (/Users/ag/Library/Application Support/fnm/node-versions/v14.17.6/installation/lib/node_modules/yarn/lib/cli.js:67038:18) + Error: https://registry.yarnpkg.com/@types%2fnext-api-middleware: Not found + at Request.params.callback [as _callback] (/Users/ag/Library/Application Support/fnm/node-versions/v14.17.6/installation/lib/node_modules/yarn/lib/cli.js:67029:18) at Request.self.callback (/Users/ag/Library/Application Support/fnm/node-versions/v14.17.6/installation/lib/node_modules/yarn/lib/cli.js:140883:22) at Request.emit (events.js:400:28) at Request. (/Users/ag/Library/Application Support/fnm/node-versions/v14.17.6/installation/lib/node_modules/yarn/lib/cli.js:141855:10) @@ -53,15 +53,18 @@ npm manifest: "@babel/preset-typescript": "^7.16.7", "@calcom/prisma": "*", "@calcom/tsconfig": "*", + "@typescript-eslint/eslint-plugin": "^5.16.0", "babel-jest": "^27.5.1", "husky": "^7.0.4", "jest": "^27.5.1", - "node-mocks-http": "^1.11.0", - "typescript": "^4.6.3" + "node-mocks-http": "^1.11.0" }, "dependencies": { "next": "^12.1.0", + "next-api-middleware": "^1.0.1", + "next-transpile-modules": "^9.0.0", "next-validations": "^0.1.11", + "typescript": "^4.6.3", "zod": "^3.14.2" } } @@ -3614,6 +3617,11 @@ Lockfile: jest-matcher-utils "^27.0.0" pretty-format "^27.0.0" + "@types/json-schema@^7.0.9": + version "7.0.10" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.10.tgz#9b05b7896166cd00e9cbd59864853abf65d9ac23" + integrity sha512-BLO9bBq59vW3fxCpD4o0N4U+DXsvwvIcl+jofw0frQo/GrBFC+/jRZj1E7kgp6dvTyNmA4y6JCV5Id/r3mNP5A== + "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" @@ -3886,6 +3894,21 @@ Lockfile: resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.3.tgz#781d360c282436494b32fe7d9f7f8e64b3118aa3" integrity sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw== + "@typescript-eslint/eslint-plugin@^5.16.0": + version "5.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.16.0.tgz#78f246dd8d1b528fc5bfca99a8a64d4023a3d86d" + integrity sha512-SJoba1edXvQRMmNI505Uo4XmGbxCK9ARQpkvOd00anxzri9RNQk0DDCxD+LIl+jYhkzOJiOMMKYEHnHEODjdCw== + dependencies: + "@typescript-eslint/scope-manager" "5.16.0" + "@typescript-eslint/type-utils" "5.16.0" + "@typescript-eslint/utils" "5.16.0" + debug "^4.3.2" + functional-red-black-tree "^1.0.1" + ignore "^5.1.8" + regexpp "^3.2.0" + semver "^7.3.5" + tsutils "^3.21.0" + "@typescript-eslint/parser@^5.0.0": version "5.14.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.14.0.tgz#7c79f898aa3cff0ceee6f1d34eeed0f034fb9ef3" @@ -3904,11 +3927,33 @@ Lockfile: "@typescript-eslint/types" "5.14.0" "@typescript-eslint/visitor-keys" "5.14.0" + "@typescript-eslint/scope-manager@5.16.0": + version "5.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.16.0.tgz#7e7909d64bd0c4d8aef629cdc764b9d3e1d3a69a" + integrity sha512-P+Yab2Hovg8NekLIR/mOElCDPyGgFZKhGoZA901Yax6WR6HVeGLbsqJkZ+Cvk5nts/dAlFKm8PfL43UZnWdpIQ== + dependencies: + "@typescript-eslint/types" "5.16.0" + "@typescript-eslint/visitor-keys" "5.16.0" + + "@typescript-eslint/type-utils@5.16.0": + version "5.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.16.0.tgz#b482bdde1d7d7c0c7080f7f2f67ea9580b9e0692" + integrity sha512-SKygICv54CCRl1Vq5ewwQUJV/8padIWvPgCxlWPGO/OgQLCijY9G7lDu6H+mqfQtbzDNlVjzVWQmeqbLMBLEwQ== + dependencies: + "@typescript-eslint/utils" "5.16.0" + debug "^4.3.2" + tsutils "^3.21.0" + "@typescript-eslint/types@5.14.0": version "5.14.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.14.0.tgz#96317cf116cea4befabc0defef371a1013f8ab11" integrity sha512-BR6Y9eE9360LNnW3eEUqAg6HxS9Q35kSIs4rp4vNHRdfg0s+/PgHgskvu5DFTM7G5VKAVjuyaN476LCPrdA7Mw== + "@typescript-eslint/types@5.16.0": + version "5.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.16.0.tgz#5827b011982950ed350f075eaecb7f47d3c643ee" + integrity sha512-oUorOwLj/3/3p/HFwrp6m/J2VfbLC8gjW5X3awpQJ/bSG+YRGFS4dpsvtQ8T2VNveV+LflQHjlLvB6v0R87z4g== + "@typescript-eslint/typescript-estree@5.14.0": version "5.14.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.14.0.tgz#78b7f7385d5b6f2748aacea5c9b7f6ae62058314" @@ -3922,6 +3967,31 @@ Lockfile: semver "^7.3.5" tsutils "^3.21.0" + "@typescript-eslint/typescript-estree@5.16.0": + version "5.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.16.0.tgz#32259459ec62f5feddca66adc695342f30101f61" + integrity sha512-SE4VfbLWUZl9MR+ngLSARptUv2E8brY0luCdgmUevU6arZRY/KxYoLI/3V/yxaURR8tLRN7bmZtJdgmzLHI6pQ== + dependencies: + "@typescript-eslint/types" "5.16.0" + "@typescript-eslint/visitor-keys" "5.16.0" + debug "^4.3.2" + globby "^11.0.4" + is-glob "^4.0.3" + semver "^7.3.5" + tsutils "^3.21.0" + + "@typescript-eslint/utils@5.16.0": + version "5.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.16.0.tgz#42218b459d6d66418a4eb199a382bdc261650679" + integrity sha512-iYej2ER6AwmejLWMWzJIHy3nPJeGDuCqf8Jnb+jAQVoPpmWzwQOfa9hWVB8GIQE5gsCv/rfN4T+AYb/V06WseQ== + dependencies: + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.16.0" + "@typescript-eslint/types" "5.16.0" + "@typescript-eslint/typescript-estree" "5.16.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + "@typescript-eslint/visitor-keys@5.14.0": version "5.14.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.14.0.tgz#1927005b3434ccd0d3ae1b2ecf60e65943c36986" @@ -3930,6 +4000,14 @@ Lockfile: "@typescript-eslint/types" "5.14.0" eslint-visitor-keys "^3.0.0" + "@typescript-eslint/visitor-keys@5.16.0": + version "5.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.16.0.tgz#f27dc3b943e6317264c7492e390c6844cd4efbbb" + integrity sha512-jqxO8msp5vZDhikTwq9ubyMHqZ67UIvawohr4qF3KhlpL7gzSjOd+8471H3nh5LyABkaI85laEKKU8SnGUK5/g== + dependencies: + "@typescript-eslint/types" "5.16.0" + eslint-visitor-keys "^3.0.0" + "@vercel/edge-functions-ui@^0.2.1": version "0.2.1" resolved "https://registry.yarnpkg.com/@vercel/edge-functions-ui/-/edge-functions-ui-0.2.1.tgz#8af0a5d8d4d544364fa79c4d075564e3a5bd972e" @@ -6383,6 +6461,14 @@ Lockfile: semver "^6.3.0" string.prototype.matchall "^4.0.6" + eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + eslint-scope@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" @@ -6477,6 +6563,11 @@ Lockfile: dependencies: estraverse "^5.2.0" + estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" @@ -7990,7 +8081,7 @@ Lockfile: resolved "https://registry.yarnpkg.com/iframe-resizer/-/iframe-resizer-4.3.2.tgz#42dd88345d18b9e377b6044dddb98c664ab0ce6b" integrity sha512-gOWo2hmdPjMQsQ+zTKbses08mDfDEMh4NneGQNP4qwePYujY1lguqP6gnbeJkf154gojWlBhIltlgnMfYjGHWA== - ignore@^5.2.0: + ignore@^5.1.8, ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== @@ -11053,6 +11144,13 @@ Lockfile: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + next-api-middleware@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/next-api-middleware/-/next-api-middleware-1.0.1.tgz#8dd76abeace9e10c6be29c9c9486a84cb649cc12" + integrity sha512-t8UbZ9UUPFB7nklrHdAusn7MfoVSHtnWlRV1R0hirvLRHPDnzidTnw1Mu99Kqvc1bVC0rQSUX5kf0j/4nmEtfA== + dependencies: + debug "^4.3.2" + next-auth@^4.0.6: version "4.3.0" resolved "https://registry.yarnpkg.com/next-auth/-/next-auth-4.3.0.tgz#93274254f9e6263c3c3edf7169a92fb13a700619"