@zenstackhq/runtime 1.0.0-beta.20 → 1.0.0-beta.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -214,143 +214,133 @@ class PolicyUtil {
214
214
  * Injects model auth guard as where clause.
215
215
  */
216
216
  injectAuthGuard(db, args, model, operation) {
217
- return __awaiter(this, void 0, void 0, function* () {
218
- let guard = this.getAuthGuard(db, model, operation);
219
- if (this.isFalse(guard)) {
217
+ let guard = this.getAuthGuard(db, model, operation);
218
+ if (this.isFalse(guard)) {
219
+ args.where = this.makeFalse();
220
+ return false;
221
+ }
222
+ if (operation === 'update' && args) {
223
+ // merge field-level policy guards
224
+ const fieldUpdateGuard = this.getFieldUpdateGuards(db, model, args);
225
+ if (fieldUpdateGuard.rejectedByField) {
226
+ // rejected
220
227
  args.where = this.makeFalse();
221
228
  return false;
222
229
  }
223
- if (operation === 'update' && args) {
224
- // merge field-level policy guards
225
- const fieldUpdateGuard = this.getFieldUpdateGuards(db, model, args);
226
- if (fieldUpdateGuard.rejectedByField) {
227
- // rejected
228
- args.where = this.makeFalse();
229
- return false;
230
- }
231
- else if (fieldUpdateGuard.guard) {
232
- // merge
233
- guard = this.and(guard, fieldUpdateGuard.guard);
234
- }
235
- }
236
- if (args.where) {
237
- // inject into relation fields:
238
- // to-many: some/none/every
239
- // to-one: direct-conditions/is/isNot
240
- yield this.injectGuardForRelationFields(db, model, args.where, operation);
230
+ else if (fieldUpdateGuard.guard) {
231
+ // merge
232
+ guard = this.and(guard, fieldUpdateGuard.guard);
241
233
  }
242
- args.where = this.and(args.where, guard);
243
- return true;
244
- });
234
+ }
235
+ if (args.where) {
236
+ // inject into relation fields:
237
+ // to-many: some/none/every
238
+ // to-one: direct-conditions/is/isNot
239
+ this.injectGuardForRelationFields(db, model, args.where, operation);
240
+ }
241
+ args.where = this.and(args.where, guard);
242
+ return true;
245
243
  }
246
244
  injectGuardForRelationFields(db, model, payload, operation) {
247
- return __awaiter(this, void 0, void 0, function* () {
248
- for (const [field, subPayload] of Object.entries(payload)) {
249
- if (!subPayload) {
250
- continue;
251
- }
252
- const fieldInfo = yield (0, model_meta_1.resolveField)(this.modelMeta, model, field);
253
- if (!fieldInfo || !fieldInfo.isDataModel) {
254
- continue;
255
- }
256
- if (fieldInfo.isArray) {
257
- yield this.injectGuardForToManyField(db, fieldInfo, subPayload, operation);
258
- }
259
- else {
260
- yield this.injectGuardForToOneField(db, fieldInfo, subPayload, operation);
261
- }
245
+ for (const [field, subPayload] of Object.entries(payload)) {
246
+ if (!subPayload) {
247
+ continue;
262
248
  }
263
- });
249
+ const fieldInfo = (0, model_meta_1.resolveField)(this.modelMeta, model, field);
250
+ if (!fieldInfo || !fieldInfo.isDataModel) {
251
+ continue;
252
+ }
253
+ if (fieldInfo.isArray) {
254
+ this.injectGuardForToManyField(db, fieldInfo, subPayload, operation);
255
+ }
256
+ else {
257
+ this.injectGuardForToOneField(db, fieldInfo, subPayload, operation);
258
+ }
259
+ }
264
260
  }
265
261
  injectGuardForToManyField(db, fieldInfo, payload, operation) {
266
- return __awaiter(this, void 0, void 0, function* () {
267
- const guard = this.getAuthGuard(db, fieldInfo.type, operation);
268
- if (payload.some) {
269
- yield this.injectGuardForRelationFields(db, fieldInfo.type, payload.some, operation);
270
- // turn "some" into: { some: { AND: [guard, payload.some] } }
271
- payload.some = this.and(payload.some, guard);
272
- }
273
- if (payload.none) {
274
- yield this.injectGuardForRelationFields(db, fieldInfo.type, payload.none, operation);
275
- // turn none into: { none: { AND: [guard, payload.none] } }
276
- payload.none = this.and(payload.none, guard);
277
- }
278
- if (payload.every &&
279
- typeof payload.every === 'object' &&
280
- // ignore empty every clause
281
- Object.keys(payload.every).length > 0) {
282
- yield this.injectGuardForRelationFields(db, fieldInfo.type, payload.every, operation);
283
- // turn "every" into: { none: { AND: [guard, { NOT: payload.every }] } }
284
- if (!payload.none) {
285
- payload.none = {};
286
- }
287
- payload.none = this.and(payload.none, guard, this.not(payload.every));
288
- delete payload.every;
289
- }
290
- });
262
+ const guard = this.getAuthGuard(db, fieldInfo.type, operation);
263
+ if (payload.some) {
264
+ this.injectGuardForRelationFields(db, fieldInfo.type, payload.some, operation);
265
+ // turn "some" into: { some: { AND: [guard, payload.some] } }
266
+ payload.some = this.and(payload.some, guard);
267
+ }
268
+ if (payload.none) {
269
+ this.injectGuardForRelationFields(db, fieldInfo.type, payload.none, operation);
270
+ // turn none into: { none: { AND: [guard, payload.none] } }
271
+ payload.none = this.and(payload.none, guard);
272
+ }
273
+ if (payload.every &&
274
+ typeof payload.every === 'object' &&
275
+ // ignore empty every clause
276
+ Object.keys(payload.every).length > 0) {
277
+ this.injectGuardForRelationFields(db, fieldInfo.type, payload.every, operation);
278
+ // turn "every" into: { none: { AND: [guard, { NOT: payload.every }] } }
279
+ if (!payload.none) {
280
+ payload.none = {};
281
+ }
282
+ payload.none = this.and(payload.none, guard, this.not(payload.every));
283
+ delete payload.every;
284
+ }
291
285
  }
292
286
  injectGuardForToOneField(db, fieldInfo, payload, operation) {
293
- return __awaiter(this, void 0, void 0, function* () {
294
- const guard = this.getAuthGuard(db, fieldInfo.type, operation);
295
- if (payload.is || payload.isNot) {
296
- if (payload.is) {
297
- yield this.injectGuardForRelationFields(db, fieldInfo.type, payload.is, operation);
298
- // turn "is" into: { is: { AND: [ originalIs, guard ] }
299
- payload.is = this.and(payload.is, guard);
300
- }
301
- if (payload.isNot) {
302
- yield this.injectGuardForRelationFields(db, fieldInfo.type, payload.isNot, operation);
303
- // turn "isNot" into: { isNot: { AND: [ originalIsNot, { NOT: guard } ] } }
304
- payload.isNot = this.and(payload.isNot, this.not(guard));
305
- delete payload.isNot;
306
- }
287
+ const guard = this.getAuthGuard(db, fieldInfo.type, operation);
288
+ if (payload.is || payload.isNot) {
289
+ if (payload.is) {
290
+ this.injectGuardForRelationFields(db, fieldInfo.type, payload.is, operation);
291
+ // turn "is" into: { is: { AND: [ originalIs, guard ] }
292
+ payload.is = this.and(payload.is, guard);
307
293
  }
308
- else {
309
- yield this.injectGuardForRelationFields(db, fieldInfo.type, payload, operation);
310
- // turn direct conditions into: { is: { AND: [ originalConditions, guard ] } }
311
- const combined = this.and((0, deepcopy_1.default)(payload), guard);
312
- Object.keys(payload).forEach((key) => delete payload[key]);
313
- payload.is = combined;
294
+ if (payload.isNot) {
295
+ this.injectGuardForRelationFields(db, fieldInfo.type, payload.isNot, operation);
296
+ // turn "isNot" into: { isNot: { AND: [ originalIsNot, { NOT: guard } ] } }
297
+ payload.isNot = this.and(payload.isNot, this.not(guard));
298
+ delete payload.isNot;
314
299
  }
315
- });
300
+ }
301
+ else {
302
+ this.injectGuardForRelationFields(db, fieldInfo.type, payload, operation);
303
+ // turn direct conditions into: { is: { AND: [ originalConditions, guard ] } }
304
+ const combined = this.and((0, deepcopy_1.default)(payload), guard);
305
+ Object.keys(payload).forEach((key) => delete payload[key]);
306
+ payload.is = combined;
307
+ }
316
308
  }
317
309
  /**
318
310
  * Injects auth guard for read operations.
319
311
  */
320
312
  injectForRead(db, model, args) {
321
- return __awaiter(this, void 0, void 0, function* () {
322
- const injected = {};
323
- if (!(yield this.injectAuthGuard(db, injected, model, 'read'))) {
324
- return false;
313
+ const injected = {};
314
+ if (!this.injectAuthGuard(db, injected, model, 'read')) {
315
+ return false;
316
+ }
317
+ if (args.where) {
318
+ // inject into relation fields:
319
+ // to-many: some/none/every
320
+ // to-one: direct-conditions/is/isNot
321
+ this.injectGuardForRelationFields(db, model, args.where, 'read');
322
+ }
323
+ if (injected.where && Object.keys(injected.where).length > 0 && !this.isTrue(injected.where)) {
324
+ if (!args.where) {
325
+ args.where = injected.where;
325
326
  }
326
- if (args.where) {
327
- // inject into relation fields:
328
- // to-many: some/none/every
329
- // to-one: direct-conditions/is/isNot
330
- yield this.injectGuardForRelationFields(db, model, args.where, 'read');
327
+ else {
328
+ this.mergeWhereClause(args.where, injected.where);
331
329
  }
332
- if (injected.where && Object.keys(injected.where).length > 0 && !this.isTrue(injected.where)) {
333
- if (!args.where) {
334
- args.where = injected.where;
335
- }
336
- else {
337
- this.mergeWhereClause(args.where, injected.where);
338
- }
330
+ }
331
+ // recursively inject read guard conditions into nested select, include, and _count
332
+ const hoistedConditions = this.injectNestedReadConditions(db, model, args);
333
+ // the injection process may generate conditions that need to be hoisted to the toplevel,
334
+ // if so, merge it with the existing where
335
+ if (hoistedConditions.length > 0) {
336
+ if (!args.where) {
337
+ args.where = this.and(...hoistedConditions);
339
338
  }
340
- // recursively inject read guard conditions into nested select, include, and _count
341
- const hoistedConditions = yield this.injectNestedReadConditions(db, model, args);
342
- // the injection process may generate conditions that need to be hoisted to the toplevel,
343
- // if so, merge it with the existing where
344
- if (hoistedConditions.length > 0) {
345
- if (!args.where) {
346
- args.where = this.and(...hoistedConditions);
347
- }
348
- else {
349
- this.mergeWhereClause(args.where, this.and(...hoistedConditions));
350
- }
339
+ else {
340
+ this.mergeWhereClause(args.where, this.and(...hoistedConditions));
351
341
  }
352
- return true;
353
- });
342
+ }
343
+ return true;
354
344
  }
355
345
  // flatten unique constraint filters
356
346
  flattenGeneratedUniqueField(model, args) {
@@ -382,130 +372,126 @@ class PolicyUtil {
382
372
  * Builds a reversed query for the given nested path.
383
373
  */
384
374
  buildReversedQuery(context) {
385
- return __awaiter(this, void 0, void 0, function* () {
386
- let result, currQuery;
387
- let currField;
388
- for (let i = context.nestingPath.length - 1; i >= 0; i--) {
389
- const { field, model, where } = context.nestingPath[i];
390
- // never modify the original where because it's shared in the structure
391
- const visitWhere = Object.assign({}, where);
392
- if (model && where) {
393
- // make sure composite unique condition is flattened
394
- this.flattenGeneratedUniqueField(model, visitWhere);
375
+ let result, currQuery;
376
+ let currField;
377
+ for (let i = context.nestingPath.length - 1; i >= 0; i--) {
378
+ const { field, model, where } = context.nestingPath[i];
379
+ // never modify the original where because it's shared in the structure
380
+ const visitWhere = Object.assign({}, where);
381
+ if (model && where) {
382
+ // make sure composite unique condition is flattened
383
+ this.flattenGeneratedUniqueField(model, visitWhere);
384
+ }
385
+ if (!result) {
386
+ // first segment (bottom), just use its where clause
387
+ result = currQuery = Object.assign({}, visitWhere);
388
+ currField = field;
389
+ }
390
+ else {
391
+ if (!currField) {
392
+ throw this.unknownError(`missing field in nested path`);
395
393
  }
396
- if (!result) {
397
- // first segment (bottom), just use its where clause
398
- result = currQuery = Object.assign({}, visitWhere);
399
- currField = field;
394
+ if (!currField.backLink) {
395
+ throw this.unknownError(`field ${currField.type}.${currField.name} doesn't have a backLink`);
396
+ }
397
+ const backLinkField = this.getModelField(currField.type, currField.backLink);
398
+ if (backLinkField === null || backLinkField === void 0 ? void 0 : backLinkField.isArray) {
399
+ // many-side of relationship, wrap with "some" query
400
+ currQuery[currField.backLink] = { some: Object.assign({}, visitWhere) };
400
401
  }
401
402
  else {
402
- if (!currField) {
403
- throw this.unknownError(`missing field in nested path`);
404
- }
405
- if (!currField.backLink) {
406
- throw this.unknownError(`field ${currField.type}.${currField.name} doesn't have a backLink`);
407
- }
408
- const backLinkField = this.getModelField(currField.type, currField.backLink);
409
- if (backLinkField === null || backLinkField === void 0 ? void 0 : backLinkField.isArray) {
410
- // many-side of relationship, wrap with "some" query
411
- currQuery[currField.backLink] = { some: Object.assign({}, visitWhere) };
412
- }
413
- else {
414
- if (where && backLinkField.isRelationOwner && backLinkField.foreignKeyMapping) {
415
- for (const [r, fk] of Object.entries(backLinkField.foreignKeyMapping)) {
416
- currQuery[fk] = visitWhere[r];
417
- }
418
- if (i > 0) {
419
- currQuery[currField.backLink] = {};
420
- }
403
+ if (where && backLinkField.isRelationOwner && backLinkField.foreignKeyMapping) {
404
+ for (const [r, fk] of Object.entries(backLinkField.foreignKeyMapping)) {
405
+ currQuery[fk] = visitWhere[r];
421
406
  }
422
- else {
423
- currQuery[currField.backLink] = Object.assign({}, visitWhere);
407
+ if (i > 0) {
408
+ currQuery[currField.backLink] = {};
424
409
  }
425
410
  }
426
- currQuery = currQuery[currField.backLink];
427
- currField = field;
411
+ else {
412
+ currQuery[currField.backLink] = Object.assign({}, visitWhere);
413
+ }
428
414
  }
415
+ currQuery = currQuery[currField.backLink];
416
+ currField = field;
429
417
  }
430
- return result;
431
- });
418
+ }
419
+ return result;
432
420
  }
433
421
  injectNestedReadConditions(db, model, args) {
434
422
  var _a;
435
- return __awaiter(this, void 0, void 0, function* () {
436
- const injectTarget = (_a = args.select) !== null && _a !== void 0 ? _a : args.include;
437
- if (!injectTarget) {
438
- return [];
439
- }
440
- if (injectTarget._count !== undefined) {
441
- // _count needs to respect read policies of related models
442
- if (injectTarget._count === true) {
443
- // include count for all relations, expand to all fields
444
- // so that we can inject guard conditions for each of them
445
- injectTarget._count = { select: {} };
446
- const modelFields = (0, model_meta_1.getFields)(this.modelMeta, model);
447
- if (modelFields) {
448
- for (const [k, v] of Object.entries(modelFields)) {
449
- if (v.isDataModel && v.isArray) {
450
- // create an entry for to-many relation
451
- injectTarget._count.select[k] = {};
452
- }
423
+ const injectTarget = (_a = args.select) !== null && _a !== void 0 ? _a : args.include;
424
+ if (!injectTarget) {
425
+ return [];
426
+ }
427
+ if (injectTarget._count !== undefined) {
428
+ // _count needs to respect read policies of related models
429
+ if (injectTarget._count === true) {
430
+ // include count for all relations, expand to all fields
431
+ // so that we can inject guard conditions for each of them
432
+ injectTarget._count = { select: {} };
433
+ const modelFields = (0, model_meta_1.getFields)(this.modelMeta, model);
434
+ if (modelFields) {
435
+ for (const [k, v] of Object.entries(modelFields)) {
436
+ if (v.isDataModel && v.isArray) {
437
+ // create an entry for to-many relation
438
+ injectTarget._count.select[k] = {};
453
439
  }
454
440
  }
455
441
  }
456
- // inject conditions for each relation
457
- for (const field of Object.keys(injectTarget._count.select)) {
458
- if (typeof injectTarget._count.select[field] !== 'object') {
459
- injectTarget._count.select[field] = {};
460
- }
461
- const fieldInfo = (0, model_meta_1.resolveField)(this.modelMeta, model, field);
462
- if (!fieldInfo) {
463
- continue;
464
- }
465
- // inject into the "where" clause inside select
466
- yield this.injectAuthGuard(db, injectTarget._count.select[field], fieldInfo.type, 'read');
467
- }
468
442
  }
469
- // collect filter conditions that should be hoisted to the toplevel
470
- const hoistedConditions = [];
471
- for (const field of (0, utils_1.getModelFields)(injectTarget)) {
443
+ // inject conditions for each relation
444
+ for (const field of Object.keys(injectTarget._count.select)) {
445
+ if (typeof injectTarget._count.select[field] !== 'object') {
446
+ injectTarget._count.select[field] = {};
447
+ }
472
448
  const fieldInfo = (0, model_meta_1.resolveField)(this.modelMeta, model, field);
473
- if (!fieldInfo || !fieldInfo.isDataModel) {
474
- // only care about relation fields
449
+ if (!fieldInfo) {
475
450
  continue;
476
451
  }
477
- let hoisted;
478
- if (fieldInfo.isArray ||
479
- // Injecting where at include/select level for nullable to-one relation is supported since Prisma 4.8.0
480
- // https://github.com/prisma/prisma/discussions/20350
481
- fieldInfo.isOptional) {
482
- if (typeof injectTarget[field] !== 'object') {
483
- injectTarget[field] = {};
484
- }
485
- // inject extra condition for to-many or nullable to-one relation
486
- yield this.injectAuthGuard(db, injectTarget[field], fieldInfo.type, 'read');
487
- // recurse
488
- const subHoisted = yield this.injectNestedReadConditions(db, fieldInfo.type, injectTarget[field]);
489
- if (subHoisted.length > 0) {
490
- // we can convert it to a where at this level
491
- injectTarget[field].where = this.and(injectTarget[field].where, ...subHoisted);
492
- }
452
+ // inject into the "where" clause inside select
453
+ this.injectAuthGuard(db, injectTarget._count.select[field], fieldInfo.type, 'read');
454
+ }
455
+ }
456
+ // collect filter conditions that should be hoisted to the toplevel
457
+ const hoistedConditions = [];
458
+ for (const field of (0, utils_1.getModelFields)(injectTarget)) {
459
+ const fieldInfo = (0, model_meta_1.resolveField)(this.modelMeta, model, field);
460
+ if (!fieldInfo || !fieldInfo.isDataModel) {
461
+ // only care about relation fields
462
+ continue;
463
+ }
464
+ let hoisted;
465
+ if (fieldInfo.isArray ||
466
+ // Injecting where at include/select level for nullable to-one relation is supported since Prisma 4.8.0
467
+ // https://github.com/prisma/prisma/discussions/20350
468
+ fieldInfo.isOptional) {
469
+ if (typeof injectTarget[field] !== 'object') {
470
+ injectTarget[field] = {};
493
471
  }
494
- else {
495
- // hoist non-nullable to-one filter to the parent level
496
- hoisted = this.getAuthGuard(db, fieldInfo.type, 'read');
497
- // recurse
498
- const subHoisted = yield this.injectNestedReadConditions(db, fieldInfo.type, injectTarget[field]);
499
- if (subHoisted.length > 0) {
500
- hoisted = this.and(hoisted, ...subHoisted);
501
- }
472
+ // inject extra condition for to-many or nullable to-one relation
473
+ this.injectAuthGuard(db, injectTarget[field], fieldInfo.type, 'read');
474
+ // recurse
475
+ const subHoisted = this.injectNestedReadConditions(db, fieldInfo.type, injectTarget[field]);
476
+ if (subHoisted.length > 0) {
477
+ // we can convert it to a where at this level
478
+ injectTarget[field].where = this.and(injectTarget[field].where, ...subHoisted);
502
479
  }
503
- if (hoisted && !this.isTrue(hoisted)) {
504
- hoistedConditions.push({ [field]: hoisted });
480
+ }
481
+ else {
482
+ // hoist non-nullable to-one filter to the parent level
483
+ hoisted = this.getAuthGuard(db, fieldInfo.type, 'read');
484
+ // recurse
485
+ const subHoisted = this.injectNestedReadConditions(db, fieldInfo.type, injectTarget[field]);
486
+ if (subHoisted.length > 0) {
487
+ hoisted = this.and(hoisted, ...subHoisted);
505
488
  }
506
489
  }
507
- return hoistedConditions;
508
- });
490
+ if (hoisted && !this.isTrue(hoisted)) {
491
+ hoistedConditions.push({ [field]: hoisted });
492
+ }
493
+ }
494
+ return hoistedConditions;
509
495
  }
510
496
  /**
511
497
  * Given a model and a unique filter, checks the operation is allowed by policies and field validations.
@@ -619,7 +605,7 @@ class PolicyUtil {
619
605
  this.flattenGeneratedUniqueField(model, uniqueFilter);
620
606
  const readArgs = { select: selectInclude.select, include: selectInclude.include, where: uniqueFilter };
621
607
  const error = this.deniedByPolicy(model, operation, 'result is not allowed to be read back', constants_1.CrudFailureReason.RESULT_NOT_READABLE);
622
- const injectResult = yield this.injectForRead(db, model, readArgs);
608
+ const injectResult = this.injectForRead(db, model, readArgs);
623
609
  if (!injectResult) {
624
610
  return { error, result: undefined };
625
611
  }
@@ -805,12 +791,6 @@ class PolicyUtil {
805
791
  if (typeof entityData !== 'object' || !entityData) {
806
792
  return;
807
793
  }
808
- // strip auxiliary fields
809
- for (const auxField of constants_1.AUXILIARY_FIELDS) {
810
- if (auxField in entityData) {
811
- delete entityData[auxField];
812
- }
813
- }
814
794
  for (const [field, fieldData] of Object.entries(entityData)) {
815
795
  if (fieldData === undefined) {
816
796
  continue;
@@ -859,6 +839,13 @@ class PolicyUtil {
859
839
  }
860
840
  }
861
841
  }
842
+ /**
843
+ * Gets information for all fields of a model.
844
+ */
845
+ getModelFields(model) {
846
+ model = (0, lower_case_first_1.lowerCaseFirst)(model);
847
+ return this.modelMeta.fields[model];
848
+ }
862
849
  /**
863
850
  * Gets information for a specific model field.
864
851
  */