@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.
- package/LICENSE +1 -1
- package/dist/client.cjs +6094 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +19 -0
- package/dist/client.d.ts +19 -0
- package/dist/client.js +6060 -0
- package/dist/client.js.map +1 -0
- package/dist/contract-DguafRNB.d.cts +1272 -0
- package/dist/contract-DguafRNB.d.ts +1272 -0
- package/dist/index.cjs +6088 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +6057 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/policy.cjs +2343 -0
- package/dist/plugins/policy.cjs.map +1 -0
- package/dist/plugins/policy.d.cts +24 -0
- package/dist/plugins/policy.d.ts +24 -0
- package/dist/plugins/policy.js +2307 -0
- package/dist/plugins/policy.js.map +1 -0
- package/dist/schema.cjs +110 -0
- package/dist/schema.cjs.map +1 -0
- package/dist/schema.d.cts +29 -0
- package/dist/schema.d.ts +29 -0
- package/dist/schema.js +85 -0
- package/dist/schema.js.map +1 -0
- package/dist/utils/pg-utils.cjs +39 -0
- package/dist/utils/pg-utils.cjs.map +1 -0
- package/dist/utils/pg-utils.d.cts +8 -0
- package/dist/utils/pg-utils.d.ts +8 -0
- package/dist/utils/pg-utils.js +16 -0
- package/dist/utils/pg-utils.js.map +1 -0
- package/{browser/index.js → dist/utils/sqlite-utils.cjs} +21 -37
- package/dist/utils/sqlite-utils.cjs.map +1 -0
- package/dist/utils/sqlite-utils.d.cts +8 -0
- package/dist/utils/sqlite-utils.d.ts +8 -0
- package/dist/utils/sqlite-utils.js +22 -0
- package/dist/utils/sqlite-utils.js.map +1 -0
- package/package.json +105 -114
- package/README.md +0 -5
- package/browser/index.d.mts +0 -13
- package/browser/index.d.ts +0 -13
- package/browser/index.js.map +0 -1
- package/browser/index.mjs +0 -33
- package/browser/index.mjs.map +0 -1
- package/constants.d.ts +0 -62
- package/constants.js +0 -76
- package/constants.js.map +0 -1
- package/cross/index.d.mts +0 -379
- package/cross/index.d.ts +0 -379
- package/cross/index.js +0 -903
- package/cross/index.js.map +0 -1
- package/cross/index.mjs +0 -861
- package/cross/index.mjs.map +0 -1
- package/edge.d.ts +0 -1
- package/edge.js +0 -18
- package/edge.js.map +0 -1
- package/encryption/index.d.ts +0 -25
- package/encryption/index.js +0 -74
- package/encryption/index.js.map +0 -1
- package/encryption/utils.d.ts +0 -9
- package/encryption/utils.js +0 -99
- package/encryption/utils.js.map +0 -1
- package/enhance-edge.d.ts +0 -1
- package/enhance-edge.js +0 -10
- package/enhance.d.ts +0 -1
- package/enhance.js +0 -10
- package/enhancements/edge/create-enhancement.d.ts +0 -42
- package/enhancements/edge/create-enhancement.js +0 -102
- package/enhancements/edge/create-enhancement.js.map +0 -1
- package/enhancements/edge/default-auth.d.ts +0 -8
- package/enhancements/edge/default-auth.js +0 -180
- package/enhancements/edge/default-auth.js.map +0 -1
- package/enhancements/edge/delegate.d.ts +0 -77
- package/enhancements/edge/delegate.js +0 -1294
- package/enhancements/edge/delegate.js.map +0 -1
- package/enhancements/edge/encryption.d.ts +0 -7
- package/enhancements/edge/encryption.js +0 -150
- package/enhancements/edge/encryption.js.map +0 -1
- package/enhancements/edge/index.d.ts +0 -4
- package/enhancements/edge/index.js +0 -21
- package/enhancements/edge/index.js.map +0 -1
- package/enhancements/edge/json-processor.d.ts +0 -7
- package/enhancements/edge/json-processor.js +0 -89
- package/enhancements/edge/json-processor.js.map +0 -1
- package/enhancements/edge/logger.d.ts +0 -29
- package/enhancements/edge/logger.js +0 -65
- package/enhancements/edge/logger.js.map +0 -1
- package/enhancements/edge/omit.d.ts +0 -7
- package/enhancements/edge/omit.js +0 -96
- package/enhancements/edge/omit.js.map +0 -1
- package/enhancements/edge/password.d.ts +0 -7
- package/enhancements/edge/password.js +0 -64
- package/enhancements/edge/password.js.map +0 -1
- package/enhancements/edge/policy/check-utils.d.ts +0 -5
- package/enhancements/edge/policy/check-utils.js +0 -20
- package/enhancements/edge/policy/check-utils.js.map +0 -1
- package/enhancements/edge/policy/handler.d.ts +0 -100
- package/enhancements/edge/policy/handler.js +0 -1442
- package/enhancements/edge/policy/handler.js.map +0 -1
- package/enhancements/edge/policy/index.d.ts +0 -19
- package/enhancements/edge/policy/index.js +0 -65
- package/enhancements/edge/policy/index.js.map +0 -1
- package/enhancements/edge/policy/policy-utils.d.ts +0 -181
- package/enhancements/edge/policy/policy-utils.js +0 -1357
- package/enhancements/edge/policy/policy-utils.js.map +0 -1
- package/enhancements/edge/promise.d.ts +0 -15
- package/enhancements/edge/promise.js +0 -99
- package/enhancements/edge/promise.js.map +0 -1
- package/enhancements/edge/proxy.d.ts +0 -120
- package/enhancements/edge/proxy.js +0 -287
- package/enhancements/edge/proxy.js.map +0 -1
- package/enhancements/edge/query-utils.d.ts +0 -53
- package/enhancements/edge/query-utils.js +0 -256
- package/enhancements/edge/query-utils.js.map +0 -1
- package/enhancements/edge/types.d.ts +0 -238
- package/enhancements/edge/types.js +0 -3
- package/enhancements/edge/types.js.map +0 -1
- package/enhancements/edge/utils.d.ts +0 -11
- package/enhancements/edge/utils.js +0 -49
- package/enhancements/edge/utils.js.map +0 -1
- package/enhancements/edge/where-visitor.d.ts +0 -32
- package/enhancements/edge/where-visitor.js +0 -86
- package/enhancements/edge/where-visitor.js.map +0 -1
- package/enhancements/node/create-enhancement.d.ts +0 -42
- package/enhancements/node/create-enhancement.js +0 -102
- package/enhancements/node/create-enhancement.js.map +0 -1
- package/enhancements/node/default-auth.d.ts +0 -8
- package/enhancements/node/default-auth.js +0 -180
- package/enhancements/node/default-auth.js.map +0 -1
- package/enhancements/node/delegate.d.ts +0 -77
- package/enhancements/node/delegate.js +0 -1294
- package/enhancements/node/delegate.js.map +0 -1
- package/enhancements/node/encryption.d.ts +0 -7
- package/enhancements/node/encryption.js +0 -150
- package/enhancements/node/encryption.js.map +0 -1
- package/enhancements/node/index.d.ts +0 -4
- package/enhancements/node/index.js +0 -21
- package/enhancements/node/index.js.map +0 -1
- package/enhancements/node/json-processor.d.ts +0 -7
- package/enhancements/node/json-processor.js +0 -89
- package/enhancements/node/json-processor.js.map +0 -1
- package/enhancements/node/logger.d.ts +0 -29
- package/enhancements/node/logger.js +0 -65
- package/enhancements/node/logger.js.map +0 -1
- package/enhancements/node/omit.d.ts +0 -7
- package/enhancements/node/omit.js +0 -96
- package/enhancements/node/omit.js.map +0 -1
- package/enhancements/node/password.d.ts +0 -7
- package/enhancements/node/password.js +0 -64
- package/enhancements/node/password.js.map +0 -1
- package/enhancements/node/policy/check-utils.d.ts +0 -5
- package/enhancements/node/policy/check-utils.js +0 -87
- package/enhancements/node/policy/check-utils.js.map +0 -1
- package/enhancements/node/policy/constraint-solver.d.ts +0 -27
- package/enhancements/node/policy/constraint-solver.js +0 -164
- package/enhancements/node/policy/constraint-solver.js.map +0 -1
- package/enhancements/node/policy/handler.d.ts +0 -100
- package/enhancements/node/policy/handler.js +0 -1442
- package/enhancements/node/policy/handler.js.map +0 -1
- package/enhancements/node/policy/index.d.ts +0 -19
- package/enhancements/node/policy/index.js +0 -65
- package/enhancements/node/policy/index.js.map +0 -1
- package/enhancements/node/policy/policy-utils.d.ts +0 -181
- package/enhancements/node/policy/policy-utils.js +0 -1357
- package/enhancements/node/policy/policy-utils.js.map +0 -1
- package/enhancements/node/promise.d.ts +0 -15
- package/enhancements/node/promise.js +0 -99
- package/enhancements/node/promise.js.map +0 -1
- package/enhancements/node/proxy.d.ts +0 -120
- package/enhancements/node/proxy.js +0 -287
- package/enhancements/node/proxy.js.map +0 -1
- package/enhancements/node/query-utils.d.ts +0 -53
- package/enhancements/node/query-utils.js +0 -256
- package/enhancements/node/query-utils.js.map +0 -1
- package/enhancements/node/types.d.ts +0 -238
- package/enhancements/node/types.js +0 -3
- package/enhancements/node/types.js.map +0 -1
- package/enhancements/node/utils.d.ts +0 -11
- package/enhancements/node/utils.js +0 -49
- package/enhancements/node/utils.js.map +0 -1
- package/enhancements/node/where-visitor.d.ts +0 -32
- package/enhancements/node/where-visitor.js +0 -86
- package/enhancements/node/where-visitor.js.map +0 -1
- package/error.d.ts +0 -11
- package/error.js +0 -22
- package/error.js.map +0 -1
- package/index.d.ts +0 -7
- package/index.js +0 -24
- package/index.js.map +0 -1
- package/model-meta.d.ts +0 -1
- package/model-meta.js +0 -10
- package/models.d.ts +0 -1
- package/models.js +0 -1
- package/types.d.ts +0 -180
- package/types.js +0 -4
- package/types.js.map +0 -1
- package/validation.d.ts +0 -24
- package/validation.js +0 -52
- package/validation.js.map +0 -1
- package/version.d.ts +0 -5
- package/version.js +0 -35
- package/version.js.map +0 -1
- package/zod/index.d.ts +0 -3
- package/zod/index.js +0 -5
- package/zod/input.d.ts +0 -1
- package/zod/input.js +0 -8
- package/zod/models.d.ts +0 -1
- package/zod/models.js +0 -8
- package/zod/objects.d.ts +0 -1
- package/zod/objects.js +0 -8
- package/zod-utils.d.ts +0 -12
- package/zod-utils.js +0 -97
- 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
|