-
-
Notifications
You must be signed in to change notification settings - Fork 127
fix: validate zod schema before update operation is executed #1051
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -467,7 +467,7 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Validates the given create payload against Zod schema if any | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private validateCreateInputSchema(model: string, data: any) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const schema = this.utils.getZodSchema(model, 'create'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (schema) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (schema && data) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const parseResult = schema.safeParse(data); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!parseResult.success) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw this.utils.deniedByPolicy( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -496,26 +496,29 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| args = this.utils.clone(args); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // do static input validation and check if post-create checks are needed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // go through create items, statically check input to determine if post-create | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // check is needed, and also validate zod schema | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let needPostCreateCheck = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const item of enumerate(args.data)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const validationResult = this.validateCreateInputSchema(this.model, item); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (validationResult !== item) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.utils.replace(item, validationResult); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const inputCheck = this.utils.checkInputGuard(this.model, item, 'create'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (inputCheck === false) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // unconditionally deny | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw this.utils.deniedByPolicy( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.model, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'create', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CrudFailureReason.ACCESS_POLICY_VIOLATION | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (inputCheck === true) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const r = this.validateCreateInputSchema(this.model, item); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (r !== item) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.utils.replace(item, r); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // unconditionally allow | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+499
to
+518
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The loop for validating and checking input guards in + private validateAndCheckInputGuards(model: string, data: any[]) {
+ let needPostCreateCheck = false;
+ for (const item of data) {
+ // Existing validation and check logic here
+ }
+ return needPostCreateCheck;
+ }
- // Original loop logic hereCommittable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (inputCheck === undefined) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // static policy check is not possible, need to do post-create check | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| needPostCreateCheck = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -786,7 +789,13 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // check if the update actually writes to this model | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let thisModelUpdate = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const updatePayload: any = (args as any).data ?? args; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const updatePayload = (args as any).data ?? args; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const validatedPayload = this.validateUpdateInputSchema(model, updatePayload); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (validatedPayload !== updatePayload) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.utils.replace(updatePayload, validatedPayload); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (updatePayload) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const key of Object.keys(updatePayload)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const field = resolveField(this.modelMeta, model, key); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -857,6 +866,8 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| args.data = this.validateUpdateInputSchema(model, args.data); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const updateGuard = this.utils.getAuthGuard(db, model, 'update'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (this.utils.isTrue(updateGuard) || this.utils.isFalse(updateGuard)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // injects simple auth guard into where clause | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -917,7 +928,10 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await _registerPostUpdateCheck(model, uniqueFilter); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // convert upsert to update | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| context.parent.update = { where: args.where, data: args.update }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| context.parent.update = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| where: args.where, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data: this.validateUpdateInputSchema(model, args.update), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| delete context.parent.upsert; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // continue visiting the new payload | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -1016,6 +1030,37 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { result, postWriteChecks }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Validates the given update payload against Zod schema if any | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private validateUpdateInputSchema(model: string, data: any) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const schema = this.utils.getZodSchema(model, 'update'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (schema && data) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // update payload can contain non-literal fields, like: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // { x: { increment: 1 } } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // we should only validate literal fields | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const literalData = Object.entries(data).reduce<any>( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (acc, [k, v]) => ({ ...acc, ...(typeof v !== 'object' ? { [k]: v } : {}) }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const parseResult = schema.safeParse(literalData); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!parseResult.success) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw this.utils.deniedByPolicy( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| model, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'update', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `input failed validation: ${fromZodError(parseResult.error)}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CrudFailureReason.DATA_VALIDATION_VIOLATION, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parseResult.error | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // schema may have transformed field values, use it to overwrite the original data | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { ...data, ...parseResult.data }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private isUnsafeMutate(model: string, args: any) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!args) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -1046,6 +1091,8 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| args = this.utils.clone(args); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.utils.injectAuthGuardAsWhere(this.prisma, args, this.model, 'update'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| args.data = this.validateUpdateInputSchema(this.model, args.data); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1094
to
+1095
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use of + // Validate only literal fields to ensure compatibility with Prisma's special update operations
args.data = this.validateUpdateInputSchema(this.model, args.data);Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (this.utils.hasAuthGuard(this.model, 'postUpdate') || this.utils.getZodSchema(this.model)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // use a transaction to do post-update checks | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const postWriteChecks: PostWriteCheckRecord[] = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
validateCreateInputSchemamethod correctly applies Zod schema validation for create operations. However, consider adding error handling for cases where Zod schema is not defined but expected, to prevent silent failures.if (schema && data) { + // Existing logic } else { + throw new Error(`Zod schema for model '${model}' and operation 'create' not found.`); }Committable suggestion