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