From 5491821baf5162ae4acd3dfd636ef454c3e8f96d Mon Sep 17 00:00:00 2001 From: Hariom Balhara Date: Thu, 3 Aug 2023 20:52:38 +0530 Subject: [PATCH] fix: Redact prisma errors (#10536) --- apps/api/lib/helpers/captureErrors.ts | 8 +++++- apps/api/lib/helpers/withMiddleware.ts | 13 +++++++--- apps/web/pages/_error.tsx | 14 +++++++--- packages/lib/redactError.ts | 26 +++++++++++++++++++ .../lib/server/getServerErrorFromUnknown.ts | 24 +++++++++++++---- .../middlewares/captureErrorsMiddleware.ts | 17 ++++++++++++ .../trpc/server/procedures/authedProcedure.ts | 7 ++--- .../trpc/server/procedures/publicProcedure.ts | 3 ++- 8 files changed, 96 insertions(+), 16 deletions(-) create mode 100644 packages/lib/redactError.ts create mode 100644 packages/trpc/server/middlewares/captureErrorsMiddleware.ts diff --git a/apps/api/lib/helpers/captureErrors.ts b/apps/api/lib/helpers/captureErrors.ts index 0d850b052b..02aa25183d 100644 --- a/apps/api/lib/helpers/captureErrors.ts +++ b/apps/api/lib/helpers/captureErrors.ts @@ -1,6 +1,8 @@ import * as Sentry from "@sentry/nextjs"; import type { NextMiddleware } from "next-api-middleware"; +import { redactError } from "@calcom/lib/redactError"; + export const captureErrors: NextMiddleware = async (_req, res, next) => { try { // Catch any errors that are thrown in remaining @@ -8,7 +10,11 @@ export const captureErrors: NextMiddleware = async (_req, res, next) => { await next(); } catch (error) { Sentry.captureException(error); - console.log(error); + const redactedError = redactError(error); + if (redactedError instanceof Error) { + res.status(400).json({ message: redactedError.message, error: redactedError }); + return; + } res.status(400).json({ message: "Something went wrong", error }); } }; diff --git a/apps/api/lib/helpers/withMiddleware.ts b/apps/api/lib/helpers/withMiddleware.ts index 6f7d7927c0..37a7b0aa3e 100644 --- a/apps/api/lib/helpers/withMiddleware.ts +++ b/apps/api/lib/helpers/withMiddleware.ts @@ -28,10 +28,17 @@ const withMiddleware = label( customPrismaClient, extendRequest, pagination: withPagination, - sentry: captureErrors, + captureErrors, }, - // The order here, determines the order of execution, put customPrismaClient before verifyApiKey always. - ["extendRequest", "sentry", "customPrismaClient", "verifyApiKey", "addRequestId"] // <-- Provide a list of middleware to call automatically + // The order here, determines the order of execution + [ + "extendRequest", + "captureErrors", + // - Put customPrismaClient before verifyApiKey always. + "customPrismaClient", + "verifyApiKey", + "addRequestId", + ] // <-- Provide a list of middleware to call automatically ); export { withMiddleware }; diff --git a/apps/web/pages/_error.tsx b/apps/web/pages/_error.tsx index fb0ce29505..65da284e7d 100644 --- a/apps/web/pages/_error.tsx +++ b/apps/web/pages/_error.tsx @@ -10,6 +10,7 @@ import React from "react"; import { getErrorFromUnknown } from "@calcom/lib/errors"; import { HttpError } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; +import { redactError } from "@calcom/lib/redactError"; import { ErrorPage } from "@components/error/error-page"; @@ -59,10 +60,17 @@ CustomError.getInitialProps = async (ctx: AugmentedNextPageContext) => { // If a HttpError message, let's override defaults if (err instanceof HttpError) { + const redactedError = redactError(err); errorInitialProps.statusCode = err.statusCode; - errorInitialProps.title = err.name; - errorInitialProps.message = err.message; - errorInitialProps.err = err; + errorInitialProps.title = redactedError.name; + errorInitialProps.message = redactedError.message; + errorInitialProps.err = { + ...redactedError, + url: err.url, + statusCode: err.statusCode, + cause: err.cause, + method: err.method, + }; } if (res) { diff --git a/packages/lib/redactError.ts b/packages/lib/redactError.ts new file mode 100644 index 0000000000..820e00e99f --- /dev/null +++ b/packages/lib/redactError.ts @@ -0,0 +1,26 @@ +import { Prisma } from "@prisma/client"; + +import logger from "@calcom/lib/logger"; + +const log = logger.getChildLogger({ prefix: [`[[redactError]`] }); + +function shouldRedact(error: T) { + return ( + error instanceof Prisma.PrismaClientInitializationError || + error instanceof Prisma.PrismaClientKnownRequestError || + error instanceof Prisma.PrismaClientUnknownRequestError || + error instanceof Prisma.PrismaClientValidationError + ); +} + +export const redactError = (error: T) => { + if (!(error instanceof Error)) { + return error; + } + log.debug("Type of Error: ", error.constructor); + if (shouldRedact(error)) { + log.error("Error: ", JSON.stringify(error)); + return new Error("An error occured while querying the database."); + } + return error; +}; diff --git a/packages/lib/server/getServerErrorFromUnknown.ts b/packages/lib/server/getServerErrorFromUnknown.ts index 1c4fa048ba..7279f9fc97 100644 --- a/packages/lib/server/getServerErrorFromUnknown.ts +++ b/packages/lib/server/getServerErrorFromUnknown.ts @@ -4,6 +4,7 @@ import type { ZodIssue } from "zod"; import { ZodError } from "zod"; import { HttpError } from "../http-error"; +import { redactError } from "../redactError"; function hasName(cause: unknown): cause is { name: string } { return !!cause && typeof cause === "object" && "name" in cause; @@ -40,20 +41,28 @@ export function getServerErrorFromUnknown(cause: unknown): HttpError { message: "Unexpected error, please reach out for our customer support.", }); } + if (cause instanceof PrismaClientKnownRequestError) { - return new HttpError({ statusCode: 400, message: cause.message, cause }); + return getHttpError({ statusCode: 400, cause }); } if (cause instanceof NotFoundError) { - return new HttpError({ statusCode: 404, message: cause.message, cause }); + return getHttpError({ statusCode: 404, cause }); } if (cause instanceof Stripe.errors.StripeInvalidRequestError) { - return new HttpError({ statusCode: 400, message: cause.message, cause }); + return getHttpError({ statusCode: 400, cause }); } if (cause instanceof HttpError) { - return cause; + const redactedCause = redactError(cause); + return { + ...redactedCause, + cause: cause.cause, + url: cause.url, + statusCode: cause.statusCode, + method: cause.method, + }; } if (cause instanceof Error) { - return new HttpError({ statusCode: 500, message: cause.message, cause }); + return getHttpError({ statusCode: 500, cause }); } if (typeof cause === "string") { // @ts-expect-error https://github.com/tc39/proposal-error-cause @@ -65,3 +74,8 @@ export function getServerErrorFromUnknown(cause: unknown): HttpError { message: `Unhandled error of type '${typeof cause}'. Please reach out for our customer support.`, }); } + +function getHttpError({ statusCode, cause }: { statusCode: number; cause: T }) { + const redacted = redactError(cause); + return new HttpError({ statusCode, message: redacted.message, cause: redacted }); +} diff --git a/packages/trpc/server/middlewares/captureErrorsMiddleware.ts b/packages/trpc/server/middlewares/captureErrorsMiddleware.ts new file mode 100644 index 0000000000..e3fe56a950 --- /dev/null +++ b/packages/trpc/server/middlewares/captureErrorsMiddleware.ts @@ -0,0 +1,17 @@ +import { redactError } from "@calcom/lib/redactError"; + +import { middleware } from "../trpc"; + +const captureErrorsMiddleware = middleware(async ({ next }) => { + const result = await next(); + if (result && !result.ok) { + const cause = result.error.cause; + if (!cause) { + return result; + } + throw redactError(cause); + } + return result; +}); + +export default captureErrorsMiddleware; diff --git a/packages/trpc/server/procedures/authedProcedure.ts b/packages/trpc/server/procedures/authedProcedure.ts index 78845080e6..7489d535d3 100644 --- a/packages/trpc/server/procedures/authedProcedure.ts +++ b/packages/trpc/server/procedures/authedProcedure.ts @@ -1,3 +1,4 @@ +import captureErrorsMiddleware from "../middlewares/captureErrorsMiddleware"; import perfMiddleware from "../middlewares/perfMiddleware"; import { isAdminMiddleware, isAuthed, isOrgAdminMiddleware } from "../middlewares/sessionMiddleware"; import { procedure } from "../trpc"; @@ -23,10 +24,10 @@ const isRateLimitedByUserIdMiddleware = ({ intervalInMs, limit }: IRateLimitOpti return next({ ctx: { user: ctx.user, session: ctx.session } }); }); */ -const authedProcedure = procedure.use(perfMiddleware).use(isAuthed); +const authedProcedure = procedure.use(captureErrorsMiddleware).use(perfMiddleware).use(isAuthed); /*export const authedRateLimitedProcedure = ({ intervalInMs, limit }: IRateLimitOptions) => authedProcedure.use(isRateLimitedByUserIdMiddleware({ intervalInMs, limit }));*/ -export const authedAdminProcedure = publicProcedure.use(isAdminMiddleware); -export const authedOrgAdminProcedure = publicProcedure.use(isOrgAdminMiddleware); +export const authedAdminProcedure = publicProcedure.use(captureErrorsMiddleware).use(isAdminMiddleware); +export const authedOrgAdminProcedure = publicProcedure.use(captureErrorsMiddleware).use(isOrgAdminMiddleware); export default authedProcedure; diff --git a/packages/trpc/server/procedures/publicProcedure.ts b/packages/trpc/server/procedures/publicProcedure.ts index 1b27a25d47..5d4c313c91 100644 --- a/packages/trpc/server/procedures/publicProcedure.ts +++ b/packages/trpc/server/procedures/publicProcedure.ts @@ -1,6 +1,7 @@ +import captureErrorsMiddleware from "../middlewares/captureErrorsMiddleware"; import perfMiddleware from "../middlewares/perfMiddleware"; import { tRPCContext } from "../trpc"; -const publicProcedure = tRPCContext.procedure.use(perfMiddleware); +const publicProcedure = tRPCContext.procedure.use(captureErrorsMiddleware).use(perfMiddleware); export default publicProcedure;