From fd8afbc418735aa9e33a2b9e0296dbff17b48712 Mon Sep 17 00:00:00 2001 From: Keith Williams Date: Fri, 13 Sep 2024 10:52:06 -0300 Subject: [PATCH] chore: Add Prisma extension to block deleteMany/updateMany with undefined 'in' (#15721) * chore: Add Prisma extension to block updateMany with undefined 'in' * Fix type * types * Added check for deleteMany as well * Improved in check * type * Updated check to only check undefined and not isArray * Renamed the guard * another rename * Changed undefined check to use typeof * Types * Added check * Update packages/prisma/extensions/disallow-undefined-in-delete-update-many.ts Co-authored-by: Hariom Balhara * Added unit tests * Added a good case * Rename * Rename fix * Types * Temporarily disabling the extension for testing * Added back the extension * Trying to also log to see if there are swallowed errors * Update * removed unused params * removed console.log --------- Co-authored-by: Hariom Balhara Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com> --- ...allow-undefined-delete-update-many.test.ts | 38 +++++++++++++++++++ .../disallow-undefined-delete-update-many.ts | 38 +++++++++++++++++++ packages/prisma/index.ts | 3 ++ 3 files changed, 79 insertions(+) create mode 100644 packages/prisma/extensions/disallow-undefined-delete-update-many.test.ts create mode 100644 packages/prisma/extensions/disallow-undefined-delete-update-many.ts diff --git a/packages/prisma/extensions/disallow-undefined-delete-update-many.test.ts b/packages/prisma/extensions/disallow-undefined-delete-update-many.test.ts new file mode 100644 index 0000000000..a7b07968f7 --- /dev/null +++ b/packages/prisma/extensions/disallow-undefined-delete-update-many.test.ts @@ -0,0 +1,38 @@ +import { describe, it, expect } from "vitest"; + +import { checkUndefinedInValue } from "./disallow-undefined-delete-update-many"; + +describe("Disallow undefined delete/update many", () => { + it("checkUndefinedInValue should throw exception when the 'in' field of where object is undefined", async () => { + const where = { + id: { + in: undefined, + }, + }; + + expect(() => checkUndefinedInValue(where)).toThrowError( + 'The "in" value for the field "id" cannot be undefined.' + ); + }); + + it("checkUndefinedInValue should throw exception when a field of where object is undefined", async () => { + const where = { + from: undefined, + }; + + expect(() => checkUndefinedInValue(where)).toThrowError( + 'The value for the field "from" cannot be undefined.' + ); + }); + + it("checkUndefinedInValue should not throw exception when the where object does not contain undefined values", async () => { + const where = { + id: { + in: [1, 2, 3], + }, + name: "test name", + }; + + checkUndefinedInValue(where); + }); +}); diff --git a/packages/prisma/extensions/disallow-undefined-delete-update-many.ts b/packages/prisma/extensions/disallow-undefined-delete-update-many.ts new file mode 100644 index 0000000000..441de4901d --- /dev/null +++ b/packages/prisma/extensions/disallow-undefined-delete-update-many.ts @@ -0,0 +1,38 @@ +import { Prisma } from "@prisma/client"; + +export const checkUndefinedInValue = (where: any) => { + if (where) { + for (const key in where) { + // INFO: Since this is for $allModels, we don't have a way to get the correct + // where type + const whereInput = where[key as any] as any; + let message; + if (whereInput === undefined) { + message = `The value for the field "${key}" cannot be undefined.`; + throw new Error(message); + } + + if (whereInput.hasOwnProperty("in") && typeof whereInput.in === "undefined") { + message = `The "in" value for the field "${key}" cannot be undefined.`; + throw new Error(message); + } + } + } +}; + +export function disallowUndefinedDeleteUpdateManyExtension() { + return Prisma.defineExtension({ + query: { + $allModels: { + async deleteMany({ args, query }) { + checkUndefinedInValue(args.where); + return query(args); + }, + async updateMany({ args, query }) { + checkUndefinedInValue(args.where); + return query(args); + }, + }, + }, + }); +} diff --git a/packages/prisma/index.ts b/packages/prisma/index.ts index d4dd9c8b19..35214516b4 100644 --- a/packages/prisma/index.ts +++ b/packages/prisma/index.ts @@ -3,6 +3,7 @@ import { PrismaClient as PrismaClientWithoutExtension } from "@prisma/client"; import { withAccelerate } from "@prisma/extension-accelerate"; import { bookingIdempotencyKeyExtension } from "./extensions/booking-idempotency-key"; +import { disallowUndefinedDeleteUpdateManyExtension } from "./extensions/disallow-undefined-delete-update-many"; import { excludePendingPaymentsExtension } from "./extensions/exclude-pending-payment-teams"; import { usageTrackingExtention } from "./extensions/usage-tracking"; import { bookingReferenceMiddleware } from "./middleware"; @@ -25,6 +26,7 @@ export const customPrisma = (options?: Prisma.PrismaClientOptions) => .$extends(usageTrackingExtention()) .$extends(excludePendingPaymentsExtension()) .$extends(bookingIdempotencyKeyExtension()) + .$extends(disallowUndefinedDeleteUpdateManyExtension()) .$extends(withAccelerate()); // If any changed on middleware server restart is required @@ -37,6 +39,7 @@ const prismaWithClientExtensions = prismaWithoutClientExtensions .$extends(usageTrackingExtention()) .$extends(excludePendingPaymentsExtension()) .$extends(bookingIdempotencyKeyExtension()) + .$extends(disallowUndefinedDeleteUpdateManyExtension()) .$extends(withAccelerate()); export const prisma = globalForPrisma.prismaWithClientExtensions || prismaWithClientExtensions;