@zenstackhq/runtime 2.15.1 → 3.0.0-alpha.0

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.
Files changed (215) hide show
  1. package/LICENSE +1 -1
  2. package/dist/client.cjs +6094 -0
  3. package/dist/client.cjs.map +1 -0
  4. package/dist/client.d.cts +19 -0
  5. package/dist/client.d.ts +19 -0
  6. package/dist/client.js +6060 -0
  7. package/dist/client.js.map +1 -0
  8. package/dist/contract-DguafRNB.d.cts +1272 -0
  9. package/dist/contract-DguafRNB.d.ts +1272 -0
  10. package/dist/index.cjs +6088 -0
  11. package/dist/index.cjs.map +1 -0
  12. package/dist/index.d.cts +14 -0
  13. package/dist/index.d.ts +14 -0
  14. package/dist/index.js +6057 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/plugins/policy.cjs +2343 -0
  17. package/dist/plugins/policy.cjs.map +1 -0
  18. package/dist/plugins/policy.d.cts +24 -0
  19. package/dist/plugins/policy.d.ts +24 -0
  20. package/dist/plugins/policy.js +2307 -0
  21. package/dist/plugins/policy.js.map +1 -0
  22. package/dist/schema.cjs +110 -0
  23. package/dist/schema.cjs.map +1 -0
  24. package/dist/schema.d.cts +29 -0
  25. package/dist/schema.d.ts +29 -0
  26. package/dist/schema.js +85 -0
  27. package/dist/schema.js.map +1 -0
  28. package/dist/utils/pg-utils.cjs +39 -0
  29. package/dist/utils/pg-utils.cjs.map +1 -0
  30. package/dist/utils/pg-utils.d.cts +8 -0
  31. package/dist/utils/pg-utils.d.ts +8 -0
  32. package/dist/utils/pg-utils.js +16 -0
  33. package/dist/utils/pg-utils.js.map +1 -0
  34. package/{browser/index.js → dist/utils/sqlite-utils.cjs} +21 -37
  35. package/dist/utils/sqlite-utils.cjs.map +1 -0
  36. package/dist/utils/sqlite-utils.d.cts +8 -0
  37. package/dist/utils/sqlite-utils.d.ts +8 -0
  38. package/dist/utils/sqlite-utils.js +22 -0
  39. package/dist/utils/sqlite-utils.js.map +1 -0
  40. package/package.json +105 -114
  41. package/README.md +0 -5
  42. package/browser/index.d.mts +0 -13
  43. package/browser/index.d.ts +0 -13
  44. package/browser/index.js.map +0 -1
  45. package/browser/index.mjs +0 -33
  46. package/browser/index.mjs.map +0 -1
  47. package/constants.d.ts +0 -62
  48. package/constants.js +0 -76
  49. package/constants.js.map +0 -1
  50. package/cross/index.d.mts +0 -379
  51. package/cross/index.d.ts +0 -379
  52. package/cross/index.js +0 -903
  53. package/cross/index.js.map +0 -1
  54. package/cross/index.mjs +0 -861
  55. package/cross/index.mjs.map +0 -1
  56. package/edge.d.ts +0 -1
  57. package/edge.js +0 -18
  58. package/edge.js.map +0 -1
  59. package/encryption/index.d.ts +0 -25
  60. package/encryption/index.js +0 -74
  61. package/encryption/index.js.map +0 -1
  62. package/encryption/utils.d.ts +0 -9
  63. package/encryption/utils.js +0 -99
  64. package/encryption/utils.js.map +0 -1
  65. package/enhance-edge.d.ts +0 -1
  66. package/enhance-edge.js +0 -10
  67. package/enhance.d.ts +0 -1
  68. package/enhance.js +0 -10
  69. package/enhancements/edge/create-enhancement.d.ts +0 -42
  70. package/enhancements/edge/create-enhancement.js +0 -102
  71. package/enhancements/edge/create-enhancement.js.map +0 -1
  72. package/enhancements/edge/default-auth.d.ts +0 -8
  73. package/enhancements/edge/default-auth.js +0 -180
  74. package/enhancements/edge/default-auth.js.map +0 -1
  75. package/enhancements/edge/delegate.d.ts +0 -77
  76. package/enhancements/edge/delegate.js +0 -1294
  77. package/enhancements/edge/delegate.js.map +0 -1
  78. package/enhancements/edge/encryption.d.ts +0 -7
  79. package/enhancements/edge/encryption.js +0 -150
  80. package/enhancements/edge/encryption.js.map +0 -1
  81. package/enhancements/edge/index.d.ts +0 -4
  82. package/enhancements/edge/index.js +0 -21
  83. package/enhancements/edge/index.js.map +0 -1
  84. package/enhancements/edge/json-processor.d.ts +0 -7
  85. package/enhancements/edge/json-processor.js +0 -89
  86. package/enhancements/edge/json-processor.js.map +0 -1
  87. package/enhancements/edge/logger.d.ts +0 -29
  88. package/enhancements/edge/logger.js +0 -65
  89. package/enhancements/edge/logger.js.map +0 -1
  90. package/enhancements/edge/omit.d.ts +0 -7
  91. package/enhancements/edge/omit.js +0 -96
  92. package/enhancements/edge/omit.js.map +0 -1
  93. package/enhancements/edge/password.d.ts +0 -7
  94. package/enhancements/edge/password.js +0 -64
  95. package/enhancements/edge/password.js.map +0 -1
  96. package/enhancements/edge/policy/check-utils.d.ts +0 -5
  97. package/enhancements/edge/policy/check-utils.js +0 -20
  98. package/enhancements/edge/policy/check-utils.js.map +0 -1
  99. package/enhancements/edge/policy/handler.d.ts +0 -100
  100. package/enhancements/edge/policy/handler.js +0 -1442
  101. package/enhancements/edge/policy/handler.js.map +0 -1
  102. package/enhancements/edge/policy/index.d.ts +0 -19
  103. package/enhancements/edge/policy/index.js +0 -65
  104. package/enhancements/edge/policy/index.js.map +0 -1
  105. package/enhancements/edge/policy/policy-utils.d.ts +0 -181
  106. package/enhancements/edge/policy/policy-utils.js +0 -1357
  107. package/enhancements/edge/policy/policy-utils.js.map +0 -1
  108. package/enhancements/edge/promise.d.ts +0 -15
  109. package/enhancements/edge/promise.js +0 -99
  110. package/enhancements/edge/promise.js.map +0 -1
  111. package/enhancements/edge/proxy.d.ts +0 -120
  112. package/enhancements/edge/proxy.js +0 -287
  113. package/enhancements/edge/proxy.js.map +0 -1
  114. package/enhancements/edge/query-utils.d.ts +0 -53
  115. package/enhancements/edge/query-utils.js +0 -256
  116. package/enhancements/edge/query-utils.js.map +0 -1
  117. package/enhancements/edge/types.d.ts +0 -238
  118. package/enhancements/edge/types.js +0 -3
  119. package/enhancements/edge/types.js.map +0 -1
  120. package/enhancements/edge/utils.d.ts +0 -11
  121. package/enhancements/edge/utils.js +0 -49
  122. package/enhancements/edge/utils.js.map +0 -1
  123. package/enhancements/edge/where-visitor.d.ts +0 -32
  124. package/enhancements/edge/where-visitor.js +0 -86
  125. package/enhancements/edge/where-visitor.js.map +0 -1
  126. package/enhancements/node/create-enhancement.d.ts +0 -42
  127. package/enhancements/node/create-enhancement.js +0 -102
  128. package/enhancements/node/create-enhancement.js.map +0 -1
  129. package/enhancements/node/default-auth.d.ts +0 -8
  130. package/enhancements/node/default-auth.js +0 -180
  131. package/enhancements/node/default-auth.js.map +0 -1
  132. package/enhancements/node/delegate.d.ts +0 -77
  133. package/enhancements/node/delegate.js +0 -1294
  134. package/enhancements/node/delegate.js.map +0 -1
  135. package/enhancements/node/encryption.d.ts +0 -7
  136. package/enhancements/node/encryption.js +0 -150
  137. package/enhancements/node/encryption.js.map +0 -1
  138. package/enhancements/node/index.d.ts +0 -4
  139. package/enhancements/node/index.js +0 -21
  140. package/enhancements/node/index.js.map +0 -1
  141. package/enhancements/node/json-processor.d.ts +0 -7
  142. package/enhancements/node/json-processor.js +0 -89
  143. package/enhancements/node/json-processor.js.map +0 -1
  144. package/enhancements/node/logger.d.ts +0 -29
  145. package/enhancements/node/logger.js +0 -65
  146. package/enhancements/node/logger.js.map +0 -1
  147. package/enhancements/node/omit.d.ts +0 -7
  148. package/enhancements/node/omit.js +0 -96
  149. package/enhancements/node/omit.js.map +0 -1
  150. package/enhancements/node/password.d.ts +0 -7
  151. package/enhancements/node/password.js +0 -64
  152. package/enhancements/node/password.js.map +0 -1
  153. package/enhancements/node/policy/check-utils.d.ts +0 -5
  154. package/enhancements/node/policy/check-utils.js +0 -87
  155. package/enhancements/node/policy/check-utils.js.map +0 -1
  156. package/enhancements/node/policy/constraint-solver.d.ts +0 -27
  157. package/enhancements/node/policy/constraint-solver.js +0 -164
  158. package/enhancements/node/policy/constraint-solver.js.map +0 -1
  159. package/enhancements/node/policy/handler.d.ts +0 -100
  160. package/enhancements/node/policy/handler.js +0 -1442
  161. package/enhancements/node/policy/handler.js.map +0 -1
  162. package/enhancements/node/policy/index.d.ts +0 -19
  163. package/enhancements/node/policy/index.js +0 -65
  164. package/enhancements/node/policy/index.js.map +0 -1
  165. package/enhancements/node/policy/policy-utils.d.ts +0 -181
  166. package/enhancements/node/policy/policy-utils.js +0 -1357
  167. package/enhancements/node/policy/policy-utils.js.map +0 -1
  168. package/enhancements/node/promise.d.ts +0 -15
  169. package/enhancements/node/promise.js +0 -99
  170. package/enhancements/node/promise.js.map +0 -1
  171. package/enhancements/node/proxy.d.ts +0 -120
  172. package/enhancements/node/proxy.js +0 -287
  173. package/enhancements/node/proxy.js.map +0 -1
  174. package/enhancements/node/query-utils.d.ts +0 -53
  175. package/enhancements/node/query-utils.js +0 -256
  176. package/enhancements/node/query-utils.js.map +0 -1
  177. package/enhancements/node/types.d.ts +0 -238
  178. package/enhancements/node/types.js +0 -3
  179. package/enhancements/node/types.js.map +0 -1
  180. package/enhancements/node/utils.d.ts +0 -11
  181. package/enhancements/node/utils.js +0 -49
  182. package/enhancements/node/utils.js.map +0 -1
  183. package/enhancements/node/where-visitor.d.ts +0 -32
  184. package/enhancements/node/where-visitor.js +0 -86
  185. package/enhancements/node/where-visitor.js.map +0 -1
  186. package/error.d.ts +0 -11
  187. package/error.js +0 -22
  188. package/error.js.map +0 -1
  189. package/index.d.ts +0 -7
  190. package/index.js +0 -24
  191. package/index.js.map +0 -1
  192. package/model-meta.d.ts +0 -1
  193. package/model-meta.js +0 -10
  194. package/models.d.ts +0 -1
  195. package/models.js +0 -1
  196. package/types.d.ts +0 -180
  197. package/types.js +0 -4
  198. package/types.js.map +0 -1
  199. package/validation.d.ts +0 -24
  200. package/validation.js +0 -52
  201. package/validation.js.map +0 -1
  202. package/version.d.ts +0 -5
  203. package/version.js +0 -35
  204. package/version.js.map +0 -1
  205. package/zod/index.d.ts +0 -3
  206. package/zod/index.js +0 -5
  207. package/zod/input.d.ts +0 -1
  208. package/zod/input.js +0 -8
  209. package/zod/models.d.ts +0 -1
  210. package/zod/models.js +0 -8
  211. package/zod/objects.d.ts +0 -1
  212. package/zod/objects.js +0 -8
  213. package/zod-utils.d.ts +0 -12
  214. package/zod-utils.js +0 -97
  215. package/zod-utils.js.map +0 -1
@@ -1,1357 +0,0 @@
1
- "use strict";
2
- /* eslint-disable @typescript-eslint/no-explicit-any */
3
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
4
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
5
- return new (P || (P = Promise))(function (resolve, reject) {
6
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
7
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
8
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
9
- step((generator = generator.apply(thisArg, _arguments || [])).next());
10
- });
11
- };
12
- var __importDefault = (this && this.__importDefault) || function (mod) {
13
- return (mod && mod.__esModule) ? mod : { "default": mod };
14
- };
15
- Object.defineProperty(exports, "__esModule", { value: true });
16
- exports.PolicyUtil = void 0;
17
- const deepmerge_1 = __importDefault(require("deepmerge"));
18
- const is_plain_object_1 = require("is-plain-object");
19
- const lower_case_first_1 = require("lower-case-first");
20
- const traverse_1 = __importDefault(require("traverse"));
21
- const upper_case_first_1 = require("upper-case-first");
22
- const zod_1 = require("zod");
23
- const zod_validation_error_1 = require("zod-validation-error");
24
- const constants_1 = require("../../../constants");
25
- const cross_1 = require("../../../cross");
26
- const version_1 = require("../../../version");
27
- const query_utils_1 = require("../query-utils");
28
- const utils_1 = require("../utils");
29
- /**
30
- * Access policy enforcement utilities
31
- */
32
- class PolicyUtil extends query_utils_1.QueryUtils {
33
- constructor(db, options, context, shouldLogQuery = false) {
34
- super(db, options);
35
- this.db = db;
36
- this.shouldLogQuery = shouldLogQuery;
37
- //#endregion
38
- //#region Auth guard
39
- this.FULL_OPEN_MODEL_POLICY = {
40
- modelLevel: {
41
- read: { guard: true },
42
- create: { guard: true, inputChecker: true },
43
- update: { guard: true },
44
- delete: { guard: true },
45
- postUpdate: { guard: true },
46
- },
47
- };
48
- this.user = context === null || context === void 0 ? void 0 : context.user;
49
- ({
50
- modelMeta: this.modelMeta,
51
- policy: this.policy,
52
- zodSchemas: this.zodSchemas,
53
- prismaModule: this.prismaModule,
54
- } = options);
55
- }
56
- //#region Logical operators
57
- /**
58
- * Creates a conjunction of a list of query conditions.
59
- */
60
- and(...conditions) {
61
- const filtered = conditions.filter((c) => c !== undefined);
62
- if (filtered.length === 0) {
63
- return this.makeTrue();
64
- }
65
- else if (filtered.length === 1) {
66
- return this.reduce(filtered[0]);
67
- }
68
- else {
69
- return this.reduce({ AND: filtered });
70
- }
71
- }
72
- /**
73
- * Creates a disjunction of a list of query conditions.
74
- */
75
- or(...conditions) {
76
- const filtered = conditions.filter((c) => c !== undefined);
77
- if (filtered.length === 0) {
78
- return this.makeFalse();
79
- }
80
- else if (filtered.length === 1) {
81
- return this.reduce(filtered[0]);
82
- }
83
- else {
84
- return this.reduce({ OR: filtered });
85
- }
86
- }
87
- /**
88
- * Creates a negation of a query condition.
89
- */
90
- not(condition) {
91
- if (condition === undefined) {
92
- return this.makeTrue();
93
- }
94
- else if (typeof condition === 'boolean') {
95
- return this.reduce(!condition);
96
- }
97
- else {
98
- return this.reduce({ NOT: condition });
99
- }
100
- }
101
- // Static True/False conditions
102
- // https://www.prisma.io/docs/concepts/components/prisma-client/null-and-undefined#the-effect-of-null-and-undefined-on-conditionals
103
- singleKey(obj, key) {
104
- if (!obj) {
105
- return false;
106
- }
107
- else {
108
- return Object.keys(obj).length === 1 && Object.keys(obj)[0] === key;
109
- }
110
- }
111
- isTrue(condition) {
112
- if (condition === null || condition === undefined || !(0, is_plain_object_1.isPlainObject)(condition)) {
113
- return false;
114
- }
115
- // {} is true
116
- if (Object.keys(condition).length === 0) {
117
- return true;
118
- }
119
- // { OR: TRUE } is true
120
- if (this.singleKey(condition, 'OR') && typeof condition.OR === 'object' && this.isTrue(condition.OR)) {
121
- return true;
122
- }
123
- // { NOT: FALSE } is true
124
- if (this.singleKey(condition, 'NOT') && typeof condition.NOT === 'object' && this.isFalse(condition.NOT)) {
125
- return true;
126
- }
127
- // { AND: [] } is true
128
- if (this.singleKey(condition, 'AND') && Array.isArray(condition.AND) && condition.AND.length === 0) {
129
- return true;
130
- }
131
- return false;
132
- }
133
- isFalse(condition) {
134
- if (condition === null || condition === undefined || !(0, is_plain_object_1.isPlainObject)(condition)) {
135
- return false;
136
- }
137
- // { AND: FALSE } is false
138
- if (this.singleKey(condition, 'AND') && typeof condition.AND === 'object' && this.isFalse(condition.AND)) {
139
- return true;
140
- }
141
- // { NOT: TRUE } is false
142
- if (this.singleKey(condition, 'NOT') && typeof condition.NOT === 'object' && this.isTrue(condition.NOT)) {
143
- return true;
144
- }
145
- // { OR: [] } is false
146
- if (this.singleKey(condition, 'OR') && Array.isArray(condition.OR) && condition.OR.length === 0) {
147
- return true;
148
- }
149
- return false;
150
- }
151
- makeTrue() {
152
- return { AND: [] };
153
- }
154
- makeFalse() {
155
- return { OR: [] };
156
- }
157
- reduce(condition) {
158
- if (condition === true || condition === undefined) {
159
- return this.makeTrue();
160
- }
161
- if (condition === false) {
162
- return this.makeFalse();
163
- }
164
- if (condition === null) {
165
- return condition;
166
- }
167
- const result = {};
168
- for (const [key, value] of Object.entries(condition)) {
169
- if (value === null || value === undefined) {
170
- result[key] = value;
171
- continue;
172
- }
173
- switch (key) {
174
- case 'AND': {
175
- const children = (0, cross_1.enumerate)(value)
176
- .map((c) => this.reduce(c))
177
- .filter((c) => c !== undefined && !this.isTrue(c));
178
- if (children.length === 0) {
179
- // { ..., AND: [] }
180
- result[key] = [];
181
- }
182
- else if (children.some((c) => this.isFalse(c))) {
183
- // { ..., AND: { OR: [] } }
184
- result[key] = this.makeFalse();
185
- }
186
- else {
187
- result[key] = !Array.isArray(value) && children.length === 1 ? children[0] : children;
188
- }
189
- break;
190
- }
191
- case 'OR': {
192
- const children = (0, cross_1.enumerate)(value)
193
- .map((c) => this.reduce(c))
194
- .filter((c) => c !== undefined && !this.isFalse(c));
195
- if (children.length === 0) {
196
- // { ..., OR: [] }
197
- result[key] = [];
198
- }
199
- else if (children.some((c) => this.isTrue(c))) {
200
- // { ..., OR: { AND: [] } }
201
- result[key] = this.makeTrue();
202
- }
203
- else {
204
- result[key] = !Array.isArray(value) && children.length === 1 ? children[0] : children;
205
- }
206
- break;
207
- }
208
- case 'NOT': {
209
- const children = (0, cross_1.enumerate)(value).map((c) => this.reduce(c));
210
- result[key] = !Array.isArray(value) && children.length === 1 ? children[0] : children;
211
- break;
212
- }
213
- default: {
214
- if (!(0, is_plain_object_1.isPlainObject)(value)) {
215
- // don't visit into non-plain object values - could be Date, array, etc.
216
- result[key] = value;
217
- }
218
- else {
219
- result[key] = this.reduce(value);
220
- }
221
- break;
222
- }
223
- }
224
- }
225
- // finally normalize constant true/false conditions
226
- if (this.isTrue(result)) {
227
- return this.makeTrue();
228
- }
229
- else if (this.isFalse(result)) {
230
- return this.makeFalse();
231
- }
232
- else {
233
- return result;
234
- }
235
- }
236
- getModelPolicyDef(model) {
237
- if (this.options.kinds && !this.options.kinds.includes('policy')) {
238
- // policy enhancement not enabled, return an fully open guard
239
- return this.FULL_OPEN_MODEL_POLICY;
240
- }
241
- const def = this.policy.policy[(0, lower_case_first_1.lowerCaseFirst)(model)];
242
- if (!def) {
243
- throw this.unknownError(`unable to load policy guard for ${model}`);
244
- }
245
- return def;
246
- }
247
- getModelGuardForOperation(model, operation) {
248
- var _a;
249
- const def = this.getModelPolicyDef(model);
250
- return (_a = def.modelLevel[operation].guard) !== null && _a !== void 0 ? _a : true;
251
- }
252
- /**
253
- * Gets pregenerated authorization guard object for a given model and operation.
254
- *
255
- * @returns true if operation is unconditionally allowed, false if unconditionally denied,
256
- * otherwise returns a guard object
257
- */
258
- getAuthGuard(db, model, operation, preValue) {
259
- const guard = this.getModelGuardForOperation(model, operation);
260
- // constant guard
261
- if (typeof guard === 'boolean') {
262
- return this.reduce(guard);
263
- }
264
- // invoke guard function
265
- const r = guard({ user: this.user, preValue }, db);
266
- return this.reduce(r);
267
- }
268
- /**
269
- * Get field-level read auth guard
270
- */
271
- getFieldReadAuthGuard(db, model, field) {
272
- var _a, _b, _c;
273
- const def = this.getModelPolicyDef(model);
274
- const guard = (_c = (_b = (_a = def.fieldLevel) === null || _a === void 0 ? void 0 : _a.read) === null || _b === void 0 ? void 0 : _b[field]) === null || _c === void 0 ? void 0 : _c.guard;
275
- if (guard === undefined) {
276
- // field access is allowed by default
277
- return this.makeTrue();
278
- }
279
- if (typeof guard === 'boolean') {
280
- return this.reduce(guard);
281
- }
282
- const r = guard({ user: this.user }, db);
283
- return this.reduce(r);
284
- }
285
- /**
286
- * Get field-level read auth guard that overrides the model-level
287
- */
288
- getFieldOverrideReadAuthGuard(db, model, field) {
289
- var _a, _b, _c;
290
- const def = this.getModelPolicyDef(model);
291
- const guard = (_c = (_b = (_a = def.fieldLevel) === null || _a === void 0 ? void 0 : _a.read) === null || _b === void 0 ? void 0 : _b[field]) === null || _c === void 0 ? void 0 : _c.overrideGuard;
292
- if (guard === undefined) {
293
- // field access is denied by default in override mode
294
- return this.makeFalse();
295
- }
296
- if (typeof guard === 'boolean') {
297
- return this.reduce(guard);
298
- }
299
- const r = guard({ user: this.user }, db);
300
- return this.reduce(r);
301
- }
302
- /**
303
- * Get field-level update auth guard
304
- */
305
- getFieldUpdateAuthGuard(db, model, field) {
306
- var _a, _b, _c;
307
- const def = this.getModelPolicyDef(model);
308
- const guard = (_c = (_b = (_a = def.fieldLevel) === null || _a === void 0 ? void 0 : _a.update) === null || _b === void 0 ? void 0 : _b[field]) === null || _c === void 0 ? void 0 : _c.guard;
309
- if (guard === undefined) {
310
- // field access is allowed by default
311
- return this.makeTrue();
312
- }
313
- if (typeof guard === 'boolean') {
314
- return this.reduce(guard);
315
- }
316
- const r = guard({ user: this.user }, db);
317
- return this.reduce(r);
318
- }
319
- /**
320
- * Get field-level update auth guard that overrides the model-level
321
- */
322
- getFieldOverrideUpdateAuthGuard(db, model, field) {
323
- var _a, _b, _c;
324
- const def = this.getModelPolicyDef(model);
325
- const guard = (_c = (_b = (_a = def.fieldLevel) === null || _a === void 0 ? void 0 : _a.update) === null || _b === void 0 ? void 0 : _b[field]) === null || _c === void 0 ? void 0 : _c.overrideGuard;
326
- if (guard === undefined) {
327
- // field access is denied by default in override mode
328
- return this.makeFalse();
329
- }
330
- if (typeof guard === 'boolean') {
331
- return this.reduce(guard);
332
- }
333
- const r = guard({ user: this.user }, db);
334
- return this.reduce(r);
335
- }
336
- /**
337
- * Checks if the given model has a policy guard for the given operation.
338
- */
339
- hasAuthGuard(model, operation) {
340
- const guard = this.getModelGuardForOperation(model, operation);
341
- return typeof guard !== 'boolean' || guard !== true;
342
- }
343
- /**
344
- * Checks if the given model has any field-level override policy guard for the given operation.
345
- */
346
- hasOverrideAuthGuard(model, operation) {
347
- var _a;
348
- if (operation !== 'read' && operation !== 'update') {
349
- return false;
350
- }
351
- const def = this.getModelPolicyDef(model);
352
- if ((_a = def.fieldLevel) === null || _a === void 0 ? void 0 : _a[operation]) {
353
- return Object.values(def.fieldLevel[operation]).some((f) => f.overrideGuard !== undefined || f.overrideEntityChecker !== undefined);
354
- }
355
- else {
356
- return false;
357
- }
358
- }
359
- /**
360
- * Checks model creation policy based on static analysis to the input args.
361
- *
362
- * @returns boolean if static analysis is enough to determine the result, undefined if not
363
- */
364
- checkInputGuard(model, args, operation) {
365
- const def = this.getModelPolicyDef(model);
366
- const guard = def.modelLevel[operation].inputChecker;
367
- if (guard === undefined) {
368
- return undefined;
369
- }
370
- if (typeof guard === 'boolean') {
371
- return guard;
372
- }
373
- return guard(args, { user: this.user });
374
- }
375
- /**
376
- * Injects model auth guard as where clause.
377
- */
378
- injectAuthGuardAsWhere(db, args, model, operation) {
379
- var _a;
380
- let guard = this.getAuthGuard(db, model, operation);
381
- if (operation === 'update' && args) {
382
- // merge field-level policy guards
383
- const fieldUpdateGuard = this.getFieldUpdateGuards(db, model, this.getFieldsWithDefinedValues((_a = args.data) !== null && _a !== void 0 ? _a : args));
384
- if (fieldUpdateGuard.rejectedByField) {
385
- // rejected
386
- args.where = this.makeFalse();
387
- return false;
388
- }
389
- else {
390
- if (fieldUpdateGuard.guard) {
391
- // merge field-level guard
392
- guard = this.and(guard, fieldUpdateGuard.guard);
393
- }
394
- if (fieldUpdateGuard.overrideGuard) {
395
- // merge field-level override guard on the top level
396
- guard = this.or(guard, fieldUpdateGuard.overrideGuard);
397
- }
398
- }
399
- }
400
- if (operation === 'read') {
401
- // merge field-level read override guards
402
- const fieldReadOverrideGuard = this.getFieldReadGuards(db, model, args);
403
- if (fieldReadOverrideGuard) {
404
- guard = this.or(guard, fieldReadOverrideGuard);
405
- }
406
- }
407
- if (this.isFalse(guard)) {
408
- args.where = this.makeFalse();
409
- return false;
410
- }
411
- let mergedGuard = guard;
412
- if (args.where) {
413
- // inject into fields:
414
- // to-many: some/none/every
415
- // to-one: direct-conditions/is/isNot
416
- // regular fields
417
- mergedGuard = this.buildReadGuardForFields(db, model, args.where, guard);
418
- }
419
- args.where = this.and(args.where, mergedGuard);
420
- return true;
421
- }
422
- // Injects guard for relation fields nested in `payload`. The `modelGuard` parameter represents the model-level guard for `model`.
423
- // The function returns a modified copy of `modelGuard` with field-level policies combined.
424
- buildReadGuardForFields(db, model, payload, modelGuard) {
425
- if (!payload || typeof payload !== 'object' || Object.keys(payload).length === 0) {
426
- return modelGuard;
427
- }
428
- const allFieldGuards = [];
429
- const allFieldOverrideGuards = [];
430
- for (const [field, subPayload] of Object.entries(payload)) {
431
- if (!subPayload) {
432
- continue;
433
- }
434
- allFieldGuards.push(this.getFieldReadAuthGuard(db, model, field));
435
- allFieldOverrideGuards.push(this.getFieldOverrideReadAuthGuard(db, model, field));
436
- const fieldInfo = (0, cross_1.resolveField)(this.modelMeta, model, field);
437
- if (fieldInfo === null || fieldInfo === void 0 ? void 0 : fieldInfo.isDataModel) {
438
- if (fieldInfo.isArray) {
439
- this.injectReadGuardForToManyField(db, fieldInfo, subPayload);
440
- }
441
- else {
442
- this.injectReadGuardForToOneField(db, fieldInfo, subPayload);
443
- }
444
- }
445
- }
446
- // all existing field-level guards must be true
447
- const mergedGuard = this.and(...allFieldGuards);
448
- // all existing field-level override guards must be true for override to take effect; override is disabled by default
449
- const mergedOverrideGuard = allFieldOverrideGuards.length === 0 ? this.makeFalse() : this.and(...allFieldOverrideGuards);
450
- // (original-guard && field-level-guard) || field-level-override-guard
451
- const updatedGuard = this.or(this.and(modelGuard, mergedGuard), mergedOverrideGuard);
452
- return updatedGuard;
453
- }
454
- injectReadGuardForToManyField(db, fieldInfo, payload) {
455
- const guard = this.getAuthGuard(db, fieldInfo.type, 'read');
456
- if (payload.some) {
457
- const mergedGuard = this.buildReadGuardForFields(db, fieldInfo.type, payload.some, guard);
458
- // turn "some" into: { some: { AND: [guard, payload.some] } }
459
- payload.some = this.and(payload.some, mergedGuard);
460
- }
461
- if (payload.none) {
462
- const mergedGuard = this.buildReadGuardForFields(db, fieldInfo.type, payload.none, guard);
463
- // turn none into: { none: { AND: [guard, payload.none] } }
464
- payload.none = this.and(payload.none, mergedGuard);
465
- }
466
- if (payload.every &&
467
- typeof payload.every === 'object' &&
468
- // ignore empty every clause
469
- Object.keys(payload.every).length > 0) {
470
- const mergedGuard = this.buildReadGuardForFields(db, fieldInfo.type, payload.every, guard);
471
- // turn "every" into: { none: { AND: [guard, { NOT: payload.every }] } }
472
- if (!payload.none) {
473
- payload.none = {};
474
- }
475
- payload.none = this.and(payload.none, mergedGuard, this.not(payload.every));
476
- delete payload.every;
477
- }
478
- }
479
- injectReadGuardForToOneField(db, fieldInfo, payload) {
480
- const guard = this.getAuthGuard(db, fieldInfo.type, 'read');
481
- // is|isNot and flat fields conditions are mutually exclusive
482
- // is and isNot can be null value
483
- if (payload.is !== undefined || payload.isNot !== undefined) {
484
- if (payload.is) {
485
- const mergedGuard = this.buildReadGuardForFields(db, fieldInfo.type, payload.is, guard);
486
- // merge guard with existing "is": { is: { AND: [originalIs, guard] } }
487
- payload.is = this.and(payload.is, mergedGuard);
488
- }
489
- if (payload.isNot) {
490
- const mergedGuard = this.buildReadGuardForFields(db, fieldInfo.type, payload.isNot, guard);
491
- // merge guard with existing "isNot": { isNot: { AND: [originalIsNot, guard] } }
492
- payload.isNot = this.and(payload.isNot, mergedGuard);
493
- }
494
- }
495
- else {
496
- const mergedGuard = this.buildReadGuardForFields(db, fieldInfo.type, payload, guard);
497
- // turn direct conditions into: { is: { AND: [ originalConditions, guard ] } }
498
- const combined = this.and((0, cross_1.clone)(payload), mergedGuard);
499
- Object.keys(payload).forEach((key) => delete payload[key]);
500
- payload.is = combined;
501
- }
502
- }
503
- /**
504
- * Injects auth guard for read operations.
505
- */
506
- injectForRead(db, model, args) {
507
- // make select and include visible to the injection
508
- const injected = { select: args.select, include: args.include };
509
- if (!this.injectAuthGuardAsWhere(db, injected, model, 'read')) {
510
- args.where = this.makeFalse();
511
- return false;
512
- }
513
- if (args.where) {
514
- // inject into fields:
515
- // to-many: some/none/every
516
- // to-one: direct-conditions/is/isNot
517
- // regular fields
518
- const mergedGuard = this.buildReadGuardForFields(db, model, args.where, {});
519
- args.where = this.mergeWhereClause(args.where, mergedGuard);
520
- }
521
- if (args.where) {
522
- if (injected.where && Object.keys(injected.where).length > 0) {
523
- // merge injected guard with the user-provided where clause
524
- args.where = this.mergeWhereClause(args.where, injected.where);
525
- }
526
- }
527
- else if (injected.where) {
528
- // no user-provided where clause, use the injected one
529
- args.where = injected.where;
530
- }
531
- // recursively inject read guard conditions into nested select, include, and _count
532
- const hoistedConditions = this.injectNestedReadConditions(db, model, args);
533
- // the injection process may generate conditions that need to be hoisted to the toplevel,
534
- // if so, merge it with the existing where
535
- if (hoistedConditions.length > 0) {
536
- if (!args.where) {
537
- args.where = this.and(...hoistedConditions);
538
- }
539
- else {
540
- args.where = this.mergeWhereClause(args.where, this.and(...hoistedConditions));
541
- }
542
- }
543
- return true;
544
- }
545
- //#endregion
546
- //#region Checker
547
- /**
548
- * Gets checker constraints for the given model and operation.
549
- */
550
- getCheckerConstraint(model, operation) {
551
- if (this.options.kinds && !this.options.kinds.includes('policy')) {
552
- // policy enhancement not enabled, return a constant true checker result
553
- return true;
554
- }
555
- const def = this.getModelPolicyDef(model);
556
- const checker = def.modelLevel[operation].permissionChecker;
557
- if (checker === undefined) {
558
- throw new Error(`Generated permission checkers not found. Please make sure the "generatePermissionChecker" option is set to true in the "@core/enhancer" plugin.`);
559
- }
560
- if (typeof checker === 'boolean') {
561
- return checker;
562
- }
563
- if (typeof checker !== 'function') {
564
- throw this.unknownError(`invalid ${operation} checker function for ${model}`);
565
- }
566
- // call checker function
567
- let result = checker({ user: this.user });
568
- // the constraint may contain "delegate" ones that should be resolved
569
- // by evaluating the corresponding checker of the delegated models
570
- const isVariableConstraint = (value) => {
571
- return value && typeof value === 'object' && value.kind === 'variable';
572
- };
573
- const isDelegateConstraint = (value) => {
574
- return value && typeof value === 'object' && value.kind === 'delegate';
575
- };
576
- // here we prefix the constraint variables coming from delegated checkers
577
- // with the relation field name to avoid conflicts
578
- const prefixConstraintVariables = (constraint, prefix) => {
579
- return (0, traverse_1.default)(constraint).map(function (value) {
580
- if (isVariableConstraint(value)) {
581
- this.update(Object.assign(Object.assign({}, value), { name: `${prefix}${value.name}` }), true);
582
- }
583
- });
584
- };
585
- // eslint-disable-next-line @typescript-eslint/no-this-alias
586
- const that = this;
587
- result = (0, traverse_1.default)(result).forEach(function (value) {
588
- if (isDelegateConstraint(value)) {
589
- const { model: delegateModel, relation, operation: delegateOp } = value;
590
- let newValue = that.getCheckerConstraint(delegateModel, delegateOp !== null && delegateOp !== void 0 ? delegateOp : operation);
591
- newValue = prefixConstraintVariables(newValue, `${relation}.`);
592
- this.update(newValue, true);
593
- }
594
- });
595
- return result;
596
- }
597
- //#endregion
598
- /**
599
- * Gets unique constraints for the given model.
600
- */
601
- getUniqueConstraints(model) {
602
- var _a, _b;
603
- return (_b = (_a = this.modelMeta.models[(0, lower_case_first_1.lowerCaseFirst)(model)]) === null || _a === void 0 ? void 0 : _a.uniqueConstraints) !== null && _b !== void 0 ? _b : {};
604
- }
605
- injectNestedReadConditions(db, model, args) {
606
- var _a;
607
- const injectTarget = (_a = args.select) !== null && _a !== void 0 ? _a : args.include;
608
- if (!injectTarget) {
609
- return [];
610
- }
611
- if (injectTarget._count !== undefined) {
612
- // _count needs to respect read policies of related models
613
- if (injectTarget._count === true) {
614
- // include count for all relations, expand to all fields
615
- // so that we can inject guard conditions for each of them
616
- injectTarget._count = { select: {} };
617
- const modelFields = (0, cross_1.getFields)(this.modelMeta, model);
618
- if (modelFields) {
619
- for (const [k, v] of Object.entries(modelFields)) {
620
- if (v.isDataModel && v.isArray) {
621
- // create an entry for to-many relation
622
- injectTarget._count.select[k] = {};
623
- }
624
- }
625
- }
626
- }
627
- // inject conditions for each relation
628
- for (const field of Object.keys(injectTarget._count.select)) {
629
- if (typeof injectTarget._count.select[field] !== 'object') {
630
- injectTarget._count.select[field] = {};
631
- }
632
- const fieldInfo = (0, cross_1.resolveField)(this.modelMeta, model, field);
633
- if (!fieldInfo) {
634
- continue;
635
- }
636
- // inject into the "where" clause inside select
637
- this.injectAuthGuardAsWhere(db, injectTarget._count.select[field], fieldInfo.type, 'read');
638
- }
639
- }
640
- // collect filter conditions that should be hoisted to the toplevel
641
- const hoistedConditions = [];
642
- for (const field of (0, cross_1.getModelFields)(injectTarget)) {
643
- if (injectTarget[field] === false) {
644
- continue;
645
- }
646
- const fieldInfo = (0, cross_1.resolveField)(this.modelMeta, model, field);
647
- if (!fieldInfo || !fieldInfo.isDataModel) {
648
- // only care about relation fields
649
- continue;
650
- }
651
- let hoisted;
652
- if (fieldInfo.isArray ||
653
- // Injecting where at include/select level for nullable to-one relation is supported since Prisma 4.8.0
654
- // https://github.com/prisma/prisma/discussions/20350
655
- fieldInfo.isOptional) {
656
- if (typeof injectTarget[field] !== 'object') {
657
- injectTarget[field] = {};
658
- }
659
- // inject extra condition for to-many or nullable to-one relation
660
- this.injectAuthGuardAsWhere(db, injectTarget[field], fieldInfo.type, 'read');
661
- // recurse
662
- const subHoisted = this.injectNestedReadConditions(db, fieldInfo.type, injectTarget[field]);
663
- if (subHoisted.length > 0) {
664
- // we can convert it to a where at this level
665
- injectTarget[field].where = this.and(injectTarget[field].where, ...subHoisted);
666
- }
667
- }
668
- else {
669
- // hoist non-nullable to-one filter to the parent level
670
- let injected = this.safeClone(injectTarget[field]);
671
- if (typeof injected !== 'object') {
672
- injected = {};
673
- }
674
- this.injectAuthGuardAsWhere(db, injected, fieldInfo.type, 'read');
675
- hoisted = injected.where;
676
- // recurse
677
- const subHoisted = this.injectNestedReadConditions(db, fieldInfo.type, injectTarget[field]);
678
- if (subHoisted.length > 0) {
679
- hoisted = this.and(hoisted, ...subHoisted);
680
- }
681
- }
682
- if (hoisted && !this.isTrue(hoisted)) {
683
- hoistedConditions.push({ [field]: hoisted });
684
- }
685
- }
686
- return hoistedConditions;
687
- }
688
- /**
689
- * Given a model and a unique filter, checks the operation is allowed by policies and field validations.
690
- * Rejects with an error if not allowed.
691
- *
692
- * This method is only called by mutation operations.
693
- */
694
- checkPolicyForUnique(model, uniqueFilter, operation, db, fieldsToUpdate, preValue) {
695
- return __awaiter(this, void 0, void 0, function* () {
696
- let guard = this.getAuthGuard(db, model, operation, preValue);
697
- if (this.isFalse(guard) && !this.hasOverrideAuthGuard(model, operation)) {
698
- throw this.deniedByPolicy(model, operation, `entity ${(0, utils_1.formatObject)(uniqueFilter, false)} failed policy check`, constants_1.CrudFailureReason.ACCESS_POLICY_VIOLATION);
699
- }
700
- let entityChecker;
701
- if (operation === 'update' && fieldsToUpdate.length > 0) {
702
- // merge field-level policy guards
703
- const fieldUpdateGuard = this.getFieldUpdateGuards(db, model, fieldsToUpdate);
704
- if (fieldUpdateGuard.rejectedByField) {
705
- // rejected
706
- throw this.deniedByPolicy(model, 'update', `entity ${(0, utils_1.formatObject)(uniqueFilter, false)} failed update policy check for field "${fieldUpdateGuard.rejectedByField}"`, constants_1.CrudFailureReason.ACCESS_POLICY_VIOLATION);
707
- }
708
- if (fieldUpdateGuard.guard) {
709
- // merge field-level guard with AND
710
- guard = this.and(guard, fieldUpdateGuard.guard);
711
- }
712
- if (fieldUpdateGuard.overrideGuard) {
713
- // merge field-level override guard with OR
714
- guard = this.or(guard, fieldUpdateGuard.overrideGuard);
715
- }
716
- // field-level entity checker
717
- entityChecker = fieldUpdateGuard.entityChecker;
718
- }
719
- // Zod schema is to be checked for "create" and "postUpdate"
720
- const schema = ['create', 'postUpdate'].includes(operation) ? this.getZodSchema(model) : undefined;
721
- // combine field-level entity checker with model-level
722
- const modelEntityChecker = this.getEntityChecker(model, operation);
723
- entityChecker = this.combineEntityChecker(entityChecker, modelEntityChecker, 'and');
724
- if (this.isTrue(guard) && !schema && !entityChecker) {
725
- // unconditionally allowed
726
- return;
727
- }
728
- let select = schema
729
- ? // need to validate against schema, need to fetch all fields
730
- undefined
731
- : // only fetch id fields
732
- this.makeIdSelection(model);
733
- if (entityChecker === null || entityChecker === void 0 ? void 0 : entityChecker.selector) {
734
- if (!select) {
735
- select = this.makeAllScalarFieldSelect(model);
736
- }
737
- select = Object.assign(Object.assign({}, select), entityChecker.selector);
738
- }
739
- let where = this.safeClone(uniqueFilter);
740
- // query args may have be of combined-id form, need to flatten it to call findFirst
741
- this.flattenGeneratedUniqueField(model, where);
742
- // query with policy guard
743
- where = this.and(where, guard);
744
- const query = { select, where };
745
- if (this.shouldLogQuery) {
746
- this.logger.info(`[policy] checking ${model} for ${operation}, \`findFirst\`:\n${(0, utils_1.formatObject)(query)}`);
747
- }
748
- const result = yield db[model].findFirst(query);
749
- if (!result) {
750
- throw this.deniedByPolicy(model, operation, `entity ${(0, utils_1.formatObject)(uniqueFilter, false)} failed policy check`, constants_1.CrudFailureReason.ACCESS_POLICY_VIOLATION);
751
- }
752
- if (entityChecker) {
753
- if (this.logger.enabled('info')) {
754
- this.logger.info(`[policy] running entity checker on ${model} for ${operation}`);
755
- }
756
- if (!entityChecker.func(result, { user: this.user, preValue })) {
757
- throw this.deniedByPolicy(model, operation, `entity ${(0, utils_1.formatObject)(uniqueFilter, false)} failed policy check`, constants_1.CrudFailureReason.ACCESS_POLICY_VIOLATION);
758
- }
759
- }
760
- if (schema) {
761
- // TODO: push down schema check to the database
762
- this.validateZodSchema(model, undefined, result, true, (err) => {
763
- throw this.deniedByPolicy(model, operation, `entity ${(0, utils_1.formatObject)(uniqueFilter, false)} failed validation: [${(0, zod_validation_error_1.fromZodError)(err)}]`, constants_1.CrudFailureReason.DATA_VALIDATION_VIOLATION, err);
764
- });
765
- }
766
- });
767
- }
768
- getEntityChecker(model, operation, field) {
769
- var _a, _b, _c;
770
- const def = this.getModelPolicyDef(model);
771
- if (field) {
772
- return (_c = (_b = (_a = def.fieldLevel) === null || _a === void 0 ? void 0 : _a[operation]) === null || _b === void 0 ? void 0 : _b[field]) === null || _c === void 0 ? void 0 : _c.entityChecker;
773
- }
774
- else {
775
- return def.modelLevel[operation].entityChecker;
776
- }
777
- }
778
- getUpdateOverrideEntityCheckerForField(model, field) {
779
- var _a, _b, _c;
780
- const def = this.getModelPolicyDef(model);
781
- return (_c = (_b = (_a = def.fieldLevel) === null || _a === void 0 ? void 0 : _a.update) === null || _b === void 0 ? void 0 : _b[field]) === null || _c === void 0 ? void 0 : _c.overrideEntityChecker;
782
- }
783
- getFieldReadGuards(db, model, args) {
784
- const allFields = Object.values((0, cross_1.getFields)(this.modelMeta, model));
785
- // all scalar fields by default
786
- let fields = allFields.filter((f) => !f.isDataModel);
787
- if (args.select) {
788
- // explicitly selected fields
789
- fields = allFields.filter((f) => { var _a; return ((_a = args.select) === null || _a === void 0 ? void 0 : _a[f.name]) === true; });
790
- }
791
- else if (args.include) {
792
- // included relations
793
- fields.push(...allFields.filter((f) => !fields.includes(f) && args.include[f.name]));
794
- }
795
- if (fields.length === 0) {
796
- // this can happen if only selecting pseudo fields like "_count"
797
- return undefined;
798
- }
799
- const allFieldGuards = fields.map((field) => this.getFieldOverrideReadAuthGuard(db, model, field.name));
800
- return this.and(...allFieldGuards);
801
- }
802
- getFieldUpdateGuards(db, model, fieldsToUpdate) {
803
- const allFieldGuards = [];
804
- const allOverrideFieldGuards = [];
805
- let entityChecker;
806
- for (const field of fieldsToUpdate) {
807
- const fieldInfo = (0, cross_1.resolveField)(this.modelMeta, model, field);
808
- if (fieldInfo === null || fieldInfo === void 0 ? void 0 : fieldInfo.isDataModel) {
809
- // relation field update should be treated as foreign key update,
810
- // fetch and merge all foreign key guards
811
- if (fieldInfo.isRelationOwner && fieldInfo.foreignKeyMapping) {
812
- const foreignKeys = Object.values(fieldInfo.foreignKeyMapping);
813
- for (const fk of foreignKeys) {
814
- const fieldGuard = this.getFieldUpdateAuthGuard(db, model, fk);
815
- if (this.isFalse(fieldGuard)) {
816
- return { guard: fieldGuard, rejectedByField: fk };
817
- }
818
- // add field guard
819
- allFieldGuards.push(fieldGuard);
820
- // add field override guard
821
- const overrideFieldGuard = this.getFieldOverrideUpdateAuthGuard(db, model, fk);
822
- allOverrideFieldGuards.push(overrideFieldGuard);
823
- }
824
- }
825
- }
826
- else {
827
- const fieldGuard = this.getFieldUpdateAuthGuard(db, model, field);
828
- if (this.isFalse(fieldGuard)) {
829
- return { guard: fieldGuard, rejectedByField: field };
830
- }
831
- // add field guard
832
- allFieldGuards.push(fieldGuard);
833
- // add field override guard
834
- const overrideFieldGuard = this.getFieldOverrideUpdateAuthGuard(db, model, field);
835
- allOverrideFieldGuards.push(overrideFieldGuard);
836
- }
837
- // merge regular and override entity checkers with OR
838
- let checker = this.getEntityChecker(model, 'update', field);
839
- const overrideChecker = this.getUpdateOverrideEntityCheckerForField(model, field);
840
- checker = this.combineEntityChecker(checker, overrideChecker, 'or');
841
- // accumulate entity checker across fields
842
- entityChecker = this.combineEntityChecker(entityChecker, checker, 'and');
843
- }
844
- const allFieldsCombined = this.and(...allFieldGuards);
845
- const allOverrideFieldsCombined = allOverrideFieldGuards.length !== 0 ? this.and(...allOverrideFieldGuards) : undefined;
846
- return {
847
- guard: allFieldsCombined,
848
- overrideGuard: allOverrideFieldsCombined,
849
- rejectedByField: undefined,
850
- entityChecker,
851
- };
852
- }
853
- combineEntityChecker(left, right, combiner) {
854
- var _a, _b;
855
- if (!left) {
856
- return right;
857
- }
858
- if (!right) {
859
- return left;
860
- }
861
- const func = combiner === 'and'
862
- ? (entity, context) => left.func(entity, context) && right.func(entity, context)
863
- : (entity, context) => left.func(entity, context) || right.func(entity, context);
864
- return {
865
- func,
866
- selector: (0, deepmerge_1.default)((_a = left.selector) !== null && _a !== void 0 ? _a : {}, (_b = right.selector) !== null && _b !== void 0 ? _b : {}),
867
- };
868
- }
869
- /**
870
- * Tries rejecting a request based on static "false" policy.
871
- */
872
- tryReject(db, model, operation) {
873
- const guard = this.getAuthGuard(db, model, operation);
874
- if (this.isFalse(guard) && !this.hasOverrideAuthGuard(model, operation)) {
875
- throw this.deniedByPolicy(model, operation, undefined, constants_1.CrudFailureReason.ACCESS_POLICY_VIOLATION);
876
- }
877
- }
878
- /**
879
- * Checks if a model exists given a unique filter.
880
- */
881
- checkExistence(db_1, model_1, uniqueFilter_1) {
882
- return __awaiter(this, arguments, void 0, function* (db, model, uniqueFilter, throwIfNotFound = false) {
883
- uniqueFilter = this.safeClone(uniqueFilter);
884
- this.flattenGeneratedUniqueField(model, uniqueFilter);
885
- if (this.shouldLogQuery) {
886
- this.logger.info(`[policy] checking ${model} existence, \`findFirst\`:\n${(0, utils_1.formatObject)(uniqueFilter)}`);
887
- }
888
- const existing = yield db[model].findFirst({
889
- where: uniqueFilter,
890
- select: this.makeIdSelection(model),
891
- });
892
- if (!existing && throwIfNotFound) {
893
- throw this.notFound(model);
894
- }
895
- return existing;
896
- });
897
- }
898
- /**
899
- * Returns an entity given a unique filter with read policy checked. Reject if not readable.
900
- */
901
- readBack(db, model, operation, selectInclude, uniqueFilter) {
902
- return __awaiter(this, void 0, void 0, function* () {
903
- uniqueFilter = this.safeClone(uniqueFilter);
904
- this.flattenGeneratedUniqueField(model, uniqueFilter);
905
- // make sure only select and include are picked
906
- const selectIncludeClean = this.pick(selectInclude, 'select', 'include');
907
- const readArgs = Object.assign(Object.assign({}, this.safeClone(selectIncludeClean)), { where: uniqueFilter });
908
- const error = this.deniedByPolicy(model, operation, 'result is not allowed to be read back', constants_1.CrudFailureReason.RESULT_NOT_READABLE);
909
- const injectResult = this.injectForRead(db, model, readArgs);
910
- if (!injectResult) {
911
- return { error, result: undefined };
912
- }
913
- // inject select needed for field-level read checks
914
- this.injectReadCheckSelect(model, readArgs);
915
- if (this.shouldLogQuery) {
916
- this.logger.info(`[policy] checking read-back, \`findFirst\` ${model}:\n${(0, utils_1.formatObject)(readArgs)}`);
917
- }
918
- const result = yield db[model].findFirst(readArgs);
919
- if (!result) {
920
- if (this.shouldLogQuery) {
921
- this.logger.info(`[policy] cannot read back ${model}`);
922
- }
923
- return { error, result: undefined };
924
- }
925
- this.postProcessForRead(result, model, selectIncludeClean);
926
- return { result, error: undefined };
927
- });
928
- }
929
- /**
930
- * Injects field selection needed for checking field-level read policy check and evaluating
931
- * entity checker into query args.
932
- */
933
- injectReadCheckSelect(model, args) {
934
- // we need to recurse into relation fields before injecting the current level, because
935
- // injection into current level can result in relation being selected/included, which
936
- // can then cause infinite recursion when we visit relation later
937
- var _a;
938
- // recurse into relation fields
939
- const visitTarget = (_a = args.select) !== null && _a !== void 0 ? _a : args.include;
940
- if (visitTarget) {
941
- for (const key of Object.keys(visitTarget)) {
942
- const field = (0, cross_1.resolveField)(this.modelMeta, model, key);
943
- if ((field === null || field === void 0 ? void 0 : field.isDataModel) && visitTarget[key]) {
944
- if (typeof visitTarget[key] !== 'object') {
945
- // v is "true", ensure it's an object
946
- visitTarget[key] = {};
947
- }
948
- this.injectReadCheckSelect(field.type, visitTarget[key]);
949
- }
950
- }
951
- }
952
- if (this.hasFieldLevelPolicy(model)) {
953
- // recursively inject selection for fields needed for field-level read checks
954
- const readFieldSelect = this.getFieldReadCheckSelector(model, args.select);
955
- if (readFieldSelect) {
956
- this.doInjectReadCheckSelect(model, args, { select: readFieldSelect });
957
- }
958
- }
959
- const entityChecker = this.getEntityChecker(model, 'read');
960
- if (entityChecker === null || entityChecker === void 0 ? void 0 : entityChecker.selector) {
961
- this.doInjectReadCheckSelect(model, args, { select: entityChecker.selector });
962
- }
963
- }
964
- doInjectReadCheckSelect(model, args, input) {
965
- var _a;
966
- // omit should be ignored to avoid interfering with field selection
967
- if (args.omit) {
968
- delete args.omit;
969
- }
970
- if (!(input === null || input === void 0 ? void 0 : input.select)) {
971
- return;
972
- }
973
- // process scalar field selections first
974
- for (const [k, v] of Object.entries(input.select)) {
975
- const field = (0, cross_1.resolveField)(this.modelMeta, model, k);
976
- if (!field || field.isDataModel) {
977
- continue;
978
- }
979
- if (v === true) {
980
- if (!args.select) {
981
- // do nothing since all scalar fields are selected by default
982
- }
983
- else if (args.include) {
984
- // do nothing since include implies selecting all scalar fields
985
- }
986
- else {
987
- args.select[k] = true;
988
- }
989
- }
990
- }
991
- // process relation selections
992
- for (const [k, v] of Object.entries(input.select)) {
993
- const field = (0, cross_1.resolveField)(this.modelMeta, model, k);
994
- if (!field || !field.isDataModel) {
995
- continue;
996
- }
997
- // prepare the next level of args
998
- let nextArgs = (_a = args.select) !== null && _a !== void 0 ? _a : args.include;
999
- if (!nextArgs) {
1000
- nextArgs = args.include = {};
1001
- }
1002
- if (!nextArgs[k] || typeof nextArgs[k] !== 'object') {
1003
- nextArgs[k] = {};
1004
- }
1005
- if (v && typeof v === 'object') {
1006
- // recurse into relation
1007
- this.doInjectReadCheckSelect(field.type, nextArgs[k], v);
1008
- }
1009
- }
1010
- }
1011
- makeAllScalarFieldSelect(model) {
1012
- const fields = this.getModelFields(model);
1013
- const result = {};
1014
- if (fields) {
1015
- Object.entries(fields).forEach(([k, v]) => {
1016
- if (!v.isDataModel) {
1017
- result[k] = true;
1018
- }
1019
- });
1020
- }
1021
- return result;
1022
- }
1023
- //#endregion
1024
- //#region Errors
1025
- deniedByPolicy(model, operation, extra, reason, zodErrors) {
1026
- const args = { clientVersion: (0, version_1.getVersion)(), code: constants_1.PrismaErrorCode.CONSTRAINT_FAILED, meta: {} };
1027
- if (reason) {
1028
- args.meta.reason = reason;
1029
- }
1030
- if (zodErrors) {
1031
- args.meta.zodErrors = zodErrors;
1032
- }
1033
- return (0, utils_1.prismaClientKnownRequestError)(this.db, this.prismaModule, `denied by policy: ${model} entities failed '${operation}' check${extra ? ', ' + extra : ''}`, args);
1034
- }
1035
- notFound(model) {
1036
- return (0, utils_1.prismaClientKnownRequestError)(this.db, this.prismaModule, `entity not found for model ${model}`, {
1037
- clientVersion: (0, version_1.getVersion)(),
1038
- code: 'P2025',
1039
- });
1040
- }
1041
- //#endregion
1042
- //#region Misc
1043
- /**
1044
- * Gets field selection for fetching pre-update entity values for the given model.
1045
- */
1046
- getPreValueSelect(model) {
1047
- const def = this.getModelPolicyDef(model);
1048
- return def.modelLevel.postUpdate.preUpdateSelector;
1049
- }
1050
- // get a merged selector object for all field-level read policies
1051
- getFieldReadCheckSelector(model, fieldSelection) {
1052
- var _a, _b, _c;
1053
- const def = this.getModelPolicyDef(model);
1054
- let result = {};
1055
- const fieldLevel = (_a = def.fieldLevel) === null || _a === void 0 ? void 0 : _a.read;
1056
- if (fieldLevel) {
1057
- for (const [field, def] of Object.entries(fieldLevel)) {
1058
- if (!fieldSelection || fieldSelection[field]) {
1059
- // field is selected, merge the field-level selector
1060
- if ((_b = def.entityChecker) === null || _b === void 0 ? void 0 : _b.selector) {
1061
- result = (0, deepmerge_1.default)(result, def.entityChecker.selector);
1062
- }
1063
- if ((_c = def.overrideEntityChecker) === null || _c === void 0 ? void 0 : _c.selector) {
1064
- result = (0, deepmerge_1.default)(result, def.overrideEntityChecker.selector);
1065
- }
1066
- }
1067
- }
1068
- }
1069
- return Object.keys(result).length > 0 ? result : undefined;
1070
- }
1071
- checkReadField(model, field, entity) {
1072
- var _a, _b, _c, _d, _e, _f;
1073
- const def = this.getModelPolicyDef(model);
1074
- // combine regular and override field-level entity checkers with OR
1075
- const checker = (_c = (_b = (_a = def.fieldLevel) === null || _a === void 0 ? void 0 : _a.read) === null || _b === void 0 ? void 0 : _b[field]) === null || _c === void 0 ? void 0 : _c.entityChecker;
1076
- const overrideChecker = (_f = (_e = (_d = def.fieldLevel) === null || _d === void 0 ? void 0 : _d.read) === null || _e === void 0 ? void 0 : _e[field]) === null || _f === void 0 ? void 0 : _f.overrideEntityChecker;
1077
- const combinedChecker = this.combineEntityChecker(checker, overrideChecker, 'or');
1078
- if (combinedChecker === undefined) {
1079
- return true;
1080
- }
1081
- else {
1082
- return combinedChecker.func(entity, { user: this.user });
1083
- }
1084
- }
1085
- hasFieldValidation(model) {
1086
- var _a, _b;
1087
- return ((_b = (_a = this.policy.validation) === null || _a === void 0 ? void 0 : _a[(0, lower_case_first_1.lowerCaseFirst)(model)]) === null || _b === void 0 ? void 0 : _b.hasValidation) === true;
1088
- }
1089
- hasFieldLevelPolicy(model) {
1090
- var _a, _b;
1091
- const def = this.getModelPolicyDef(model);
1092
- return Object.keys((_b = (_a = def.fieldLevel) === null || _a === void 0 ? void 0 : _a.read) !== null && _b !== void 0 ? _b : {}).length > 0;
1093
- }
1094
- /**
1095
- * Gets Zod schema for the given model and access kind.
1096
- *
1097
- * @param kind kind of Zod schema to get for. If undefined, returns the full schema.
1098
- */
1099
- getZodSchema(model, excludePasswordFields = true, kind = undefined) {
1100
- if (!this.zodSchemas) {
1101
- return undefined;
1102
- }
1103
- if (!this.hasFieldValidation(model)) {
1104
- return undefined;
1105
- }
1106
- const schemaKey = `${(0, upper_case_first_1.upperCaseFirst)(model)}${kind ? 'Prisma' + (0, upper_case_first_1.upperCaseFirst)(kind) : ''}Schema`;
1107
- if (excludePasswordFields) {
1108
- // The `excludePasswordFields` mode is to handle the issue the fields marked with `@password` change at runtime,
1109
- // so they can only be fully validated when processing the input of "create" and "update" operations.
1110
- //
1111
- // When excluding them, we need to override them with plain string schemas. However, since the scheme is not always
1112
- // an `ZodObject` (this happens when there's `@@validate` refinement), we need to fetch the `ZodObject` schema before
1113
- // the refinement is applied, override the `@password` fields and then re-apply the refinement.
1114
- let schema;
1115
- const overridePasswordFields = (schema) => {
1116
- var _a, _b;
1117
- let result = schema;
1118
- const modelFields = (_a = this.modelMeta.models[(0, lower_case_first_1.lowerCaseFirst)(model)]) === null || _a === void 0 ? void 0 : _a.fields;
1119
- if (modelFields) {
1120
- for (const [key, field] of Object.entries(modelFields)) {
1121
- if ((_b = field.attributes) === null || _b === void 0 ? void 0 : _b.some((attr) => attr.name === '@password')) {
1122
- // override `@password` field schema with a string schema
1123
- let pwFieldSchema = zod_1.z.string();
1124
- if (field.isOptional) {
1125
- pwFieldSchema = pwFieldSchema.nullish();
1126
- }
1127
- result = result.merge(zod_1.z.object({ [key]: pwFieldSchema }));
1128
- }
1129
- }
1130
- }
1131
- return result;
1132
- };
1133
- // get the schema without refinement: `[Model]WithoutRefineSchema`
1134
- const withoutRefineSchemaKey = `${(0, upper_case_first_1.upperCaseFirst)(model)}${kind ? 'Prisma' + (0, upper_case_first_1.upperCaseFirst)(kind) : ''}WithoutRefineSchema`;
1135
- schema = this.zodSchemas.models[withoutRefineSchemaKey];
1136
- if (schema) {
1137
- // the schema has refinement, need to call refine function after schema merge
1138
- schema = overridePasswordFields(schema);
1139
- // refine function: `refine[Model]`
1140
- const refineFuncKey = `refine${(0, upper_case_first_1.upperCaseFirst)(model)}`;
1141
- const refineFunc = this.zodSchemas.models[refineFuncKey];
1142
- return typeof refineFunc === 'function' ? refineFunc(schema) : schema;
1143
- }
1144
- else {
1145
- // otherwise, directly override the `@password` fields
1146
- schema = this.zodSchemas.models[schemaKey];
1147
- return schema ? overridePasswordFields(schema) : undefined;
1148
- }
1149
- }
1150
- else {
1151
- // simply return the schema
1152
- return this.zodSchemas.models[schemaKey];
1153
- }
1154
- }
1155
- /**
1156
- * Validates the given data against the Zod schema for the given model and kind.
1157
- *
1158
- * @param model model
1159
- * @param kind validation kind. Pass undefined to validate against the full schema.
1160
- * @param data input data
1161
- * @param excludePasswordFields whether exclude schema validation for `@password` fields
1162
- * @param onError error callback
1163
- * @returns Zod-validated data
1164
- */
1165
- validateZodSchema(model, kind, data, excludePasswordFields, onError) {
1166
- const schema = this.getZodSchema(model, excludePasswordFields, kind);
1167
- if (!schema) {
1168
- return data;
1169
- }
1170
- const parseResult = schema.safeParse(data);
1171
- if (!parseResult.success) {
1172
- if (this.logger.enabled('info')) {
1173
- this.logger.info(`entity ${model} failed validation for operation ${kind}: ${(0, zod_validation_error_1.fromZodError)(parseResult.error)}`);
1174
- }
1175
- onError(parseResult.error);
1176
- return undefined;
1177
- }
1178
- return parseResult.data;
1179
- }
1180
- /**
1181
- * Post processing checks and clean-up for read model entities.
1182
- */
1183
- postProcessForRead(data, model, queryArgs) {
1184
- // preserve the original data as it may be needed for checking field-level readability,
1185
- // while the "data" will be manipulated during traversal (deleting unreadable fields)
1186
- const origData = this.safeClone(data);
1187
- // use the concrete model if the data is a polymorphic entity
1188
- const realModel = this.getDelegateConcreteModel(model, data);
1189
- return this.doPostProcessForRead(data, realModel, origData, queryArgs, this.hasFieldLevelPolicy(realModel));
1190
- }
1191
- doPostProcessForRead(data, model, fullData, queryArgs, hasFieldLevelPolicy, path = '') {
1192
- var _a, _b, _c;
1193
- if (data === null || data === undefined) {
1194
- return data;
1195
- }
1196
- let filteredData = data;
1197
- let filteredFullData = fullData;
1198
- const entityChecker = this.getEntityChecker(model, 'read');
1199
- if (entityChecker) {
1200
- if (Array.isArray(data)) {
1201
- filteredData = [];
1202
- filteredFullData = [];
1203
- for (const [entityData, entityFullData] of (0, cross_1.zip)(data, fullData)) {
1204
- if (!entityChecker.func(entityData, { user: this.user })) {
1205
- if (this.shouldLogQuery) {
1206
- this.logger.info(`[policy] dropping ${model} entity${path ? ' at ' + path : ''} due to entity checker`);
1207
- }
1208
- }
1209
- else {
1210
- filteredData.push(entityData);
1211
- filteredFullData.push(entityFullData);
1212
- }
1213
- }
1214
- }
1215
- else {
1216
- if (!entityChecker.func(data, { user: this.user })) {
1217
- if (this.shouldLogQuery) {
1218
- this.logger.info(`[policy] dropping ${model} entity${path ? ' at ' + path : ''} due to entity checker`);
1219
- }
1220
- return null;
1221
- }
1222
- }
1223
- }
1224
- for (const [entityData, entityFullData] of (0, cross_1.zip)(filteredData, filteredFullData)) {
1225
- if (typeof entityData !== 'object' || !entityData) {
1226
- continue;
1227
- }
1228
- for (const [field, fieldData] of Object.entries(entityData)) {
1229
- if (fieldData === undefined) {
1230
- continue;
1231
- }
1232
- const fieldInfo = (0, cross_1.resolveField)(this.modelMeta, model, field);
1233
- if (!fieldInfo) {
1234
- // could be _count, etc.
1235
- continue;
1236
- }
1237
- if (((_a = queryArgs === null || queryArgs === void 0 ? void 0 : queryArgs.omit) === null || _a === void 0 ? void 0 : _a[field]) === true) {
1238
- // respect `{ omit: { [field]: true } }`
1239
- delete entityData[field];
1240
- continue;
1241
- }
1242
- if (hasFieldLevelPolicy) {
1243
- // 1. remove fields selected for checking field-level policies but not selected by the original query args
1244
- // 2. evaluate field-level policies and remove fields that are not readable
1245
- if (!fieldInfo.isDataModel) {
1246
- // scalar field, delete unselected ones
1247
- const select = queryArgs === null || queryArgs === void 0 ? void 0 : queryArgs.select;
1248
- if (select && typeof select === 'object' && select[field] !== true) {
1249
- // there's a select clause but this field is not included
1250
- delete entityData[field];
1251
- continue;
1252
- }
1253
- }
1254
- else {
1255
- // relation field, delete if not selected or included
1256
- const include = queryArgs === null || queryArgs === void 0 ? void 0 : queryArgs.include;
1257
- const select = queryArgs === null || queryArgs === void 0 ? void 0 : queryArgs.select;
1258
- if (!(include === null || include === void 0 ? void 0 : include[field]) && !(select === null || select === void 0 ? void 0 : select[field])) {
1259
- // relation field not included or selected
1260
- delete entityData[field];
1261
- continue;
1262
- }
1263
- }
1264
- // delete unreadable fields
1265
- if (!this.checkReadField(model, field, entityFullData)) {
1266
- if (this.shouldLogQuery) {
1267
- this.logger.info(`[policy] dropping unreadable field ${path ? path + '.' : ''}${field}`);
1268
- }
1269
- delete entityData[field];
1270
- continue;
1271
- }
1272
- }
1273
- if (fieldInfo.isDataModel) {
1274
- // recurse into nested fields
1275
- const nextArgs = (_c = ((_b = queryArgs === null || queryArgs === void 0 ? void 0 : queryArgs.select) !== null && _b !== void 0 ? _b : queryArgs === null || queryArgs === void 0 ? void 0 : queryArgs.include)) === null || _c === void 0 ? void 0 : _c[field];
1276
- const nestedResult = this.doPostProcessForRead(fieldData, fieldInfo.type, entityFullData[field], nextArgs, this.hasFieldLevelPolicy(fieldInfo.type), path ? path + '.' + field : field);
1277
- if (nestedResult === undefined) {
1278
- delete entityData[field];
1279
- }
1280
- else {
1281
- entityData[field] = nestedResult;
1282
- }
1283
- }
1284
- }
1285
- }
1286
- return filteredData;
1287
- }
1288
- /**
1289
- * Replace content of `target` object with `withObject` in-place.
1290
- */
1291
- replace(target, withObject) {
1292
- if (!target || typeof target !== 'object' || !withObject || typeof withObject !== 'object') {
1293
- return;
1294
- }
1295
- // remove missing keys
1296
- for (const key of Object.keys(target)) {
1297
- if (!(key in withObject)) {
1298
- delete target[key];
1299
- }
1300
- }
1301
- // overwrite keys
1302
- for (const [key, value] of Object.entries(withObject)) {
1303
- target[key] = value;
1304
- }
1305
- }
1306
- /**
1307
- * Picks properties from an object.
1308
- */
1309
- pick(value, ...props) {
1310
- const v = value;
1311
- return props.reduce(function (result, prop) {
1312
- if (prop in v) {
1313
- result[prop] = v[prop];
1314
- }
1315
- return result;
1316
- }, {});
1317
- }
1318
- mergeWhereClause(where, extra) {
1319
- var _a;
1320
- if (!where) {
1321
- throw new Error('invalid where clause');
1322
- }
1323
- if (this.isTrue(extra)) {
1324
- return where;
1325
- }
1326
- if (this.isFalse(extra)) {
1327
- return this.makeFalse();
1328
- }
1329
- // instead of simply wrapping with AND, we preserve the structure
1330
- // of the original where clause and merge `extra` into it so that
1331
- // unique query can continue working
1332
- if (where.AND) {
1333
- // merge into existing AND clause
1334
- const conditions = Array.isArray(where.AND) ? [...where.AND] : [where.AND];
1335
- conditions.push(extra);
1336
- const combined = this.and(...conditions);
1337
- // make sure the merging always goes under AND
1338
- return Object.assign(Object.assign({}, where), { AND: (_a = combined.AND) !== null && _a !== void 0 ? _a : combined });
1339
- }
1340
- else {
1341
- // insert an AND clause
1342
- return Object.assign(Object.assign({}, where), { AND: [extra] });
1343
- }
1344
- }
1345
- /**
1346
- * Given an entity data, returns an object only containing id fields.
1347
- */
1348
- getIdFieldValues(model, data) {
1349
- if (!data) {
1350
- return undefined;
1351
- }
1352
- const idFields = this.getIdFields(model);
1353
- return Object.fromEntries(idFields.map((f) => [f.name, data[f.name]]));
1354
- }
1355
- }
1356
- exports.PolicyUtil = PolicyUtil;
1357
- //# sourceMappingURL=policy-utils.js.map