@zenstackhq/plugin-policy 3.0.0-beta.19 → 3.0.0-beta.20
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/dist/index.cjs +73 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +31 -19
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { ExpressionWrapper as ExpressionWrapper2, ValueNode as ValueNode4 } from
|
|
|
8
8
|
|
|
9
9
|
// src/policy-handler.ts
|
|
10
10
|
import { invariant as invariant3 } from "@zenstackhq/common-helpers";
|
|
11
|
-
import { getCrudDialect as getCrudDialect2,
|
|
11
|
+
import { getCrudDialect as getCrudDialect2, QueryUtils as QueryUtils2, RejectedByPolicyReason, SchemaUtils } from "@zenstackhq/orm";
|
|
12
12
|
import { ExpressionUtils as ExpressionUtils4 } from "@zenstackhq/orm/schema";
|
|
13
13
|
import { AliasNode as AliasNode3, BinaryOperationNode as BinaryOperationNode3, ColumnNode as ColumnNode2, DeleteQueryNode, expressionBuilder as expressionBuilder2, ExpressionWrapper, FromNode as FromNode2, FunctionNode as FunctionNode3, IdentifierNode as IdentifierNode2, InsertQueryNode, OperationNodeTransformer, OperatorNode as OperatorNode3, ParensNode as ParensNode2, PrimitiveValueListNode, RawNode, ReferenceNode as ReferenceNode3, ReturningNode, SelectAllNode, SelectionNode as SelectionNode2, SelectQueryNode as SelectQueryNode2, sql, TableNode as TableNode3, UpdateQueryNode, ValueListNode as ValueListNode2, ValueNode as ValueNode3, ValuesNode, WhereNode as WhereNode2 } from "kysely";
|
|
14
14
|
import { match as match3 } from "ts-pattern";
|
|
@@ -34,7 +34,7 @@ var ColumnCollector = class extends KyselyUtils.DefaultOperationNodeVisitor {
|
|
|
34
34
|
|
|
35
35
|
// src/expression-transformer.ts
|
|
36
36
|
import { invariant as invariant2 } from "@zenstackhq/common-helpers";
|
|
37
|
-
import { getCrudDialect,
|
|
37
|
+
import { getCrudDialect, QueryUtils } from "@zenstackhq/orm";
|
|
38
38
|
import { ExpressionUtils as ExpressionUtils3 } from "@zenstackhq/orm/schema";
|
|
39
39
|
import { AliasNode as AliasNode2, BinaryOperationNode as BinaryOperationNode2, ColumnNode, expressionBuilder, FromNode, FunctionNode as FunctionNode2, IdentifierNode, OperatorNode as OperatorNode2, ReferenceNode as ReferenceNode2, SelectionNode, SelectQueryNode, TableNode as TableNode2, ValueListNode, ValueNode as ValueNode2, WhereNode } from "kysely";
|
|
40
40
|
import { match as match2 } from "ts-pattern";
|
|
@@ -111,6 +111,7 @@ var ExpressionEvaluator = class {
|
|
|
111
111
|
};
|
|
112
112
|
|
|
113
113
|
// src/utils.ts
|
|
114
|
+
import { ORMError, ORMErrorReason } from "@zenstackhq/orm";
|
|
114
115
|
import { ExpressionUtils as ExpressionUtils2 } from "@zenstackhq/orm/schema";
|
|
115
116
|
import { AliasNode, AndNode, BinaryOperationNode, FunctionNode, OperatorNode, OrNode, ParensNode, ReferenceNode, TableNode, UnaryOperationNode, ValueNode } from "kysely";
|
|
116
117
|
function trueNode(dialect) {
|
|
@@ -212,6 +213,17 @@ function isBeforeInvocation(expr2) {
|
|
|
212
213
|
return ExpressionUtils2.isCall(expr2) && expr2.function === "before";
|
|
213
214
|
}
|
|
214
215
|
__name(isBeforeInvocation, "isBeforeInvocation");
|
|
216
|
+
function createRejectedByPolicyError(model, reason, message) {
|
|
217
|
+
const err = new ORMError(ORMErrorReason.REJECTED_BY_POLICY, message ?? "operation is rejected by access policies");
|
|
218
|
+
err.rejectedByPolicyReason = reason;
|
|
219
|
+
err.model = model;
|
|
220
|
+
return err;
|
|
221
|
+
}
|
|
222
|
+
__name(createRejectedByPolicyError, "createRejectedByPolicyError");
|
|
223
|
+
function createUnsupportedError(message) {
|
|
224
|
+
return new ORMError(ORMErrorReason.NOT_SUPPORTED, message);
|
|
225
|
+
}
|
|
226
|
+
__name(createUnsupportedError, "createUnsupportedError");
|
|
215
227
|
|
|
216
228
|
// src/expression-transformer.ts
|
|
217
229
|
function _ts_decorate(decorators, target, key, desc) {
|
|
@@ -256,7 +268,7 @@ var ExpressionTransformer = class {
|
|
|
256
268
|
}
|
|
257
269
|
get authType() {
|
|
258
270
|
if (!this.schema.authType) {
|
|
259
|
-
|
|
271
|
+
invariant2(false, 'Schema does not have an "authType" specified');
|
|
260
272
|
}
|
|
261
273
|
return this.schema.authType;
|
|
262
274
|
}
|
|
@@ -424,7 +436,7 @@ var ExpressionTransformer = class {
|
|
|
424
436
|
}
|
|
425
437
|
transformAuthBinary(expr2, context) {
|
|
426
438
|
if (expr2.op !== "==" && expr2.op !== "!=") {
|
|
427
|
-
throw
|
|
439
|
+
throw createUnsupportedError(`Unsupported operator for \`auth()\` in policy of model "${context.model}": ${expr2.op}`);
|
|
428
440
|
}
|
|
429
441
|
let authExpr;
|
|
430
442
|
let other;
|
|
@@ -440,7 +452,7 @@ var ExpressionTransformer = class {
|
|
|
440
452
|
} else {
|
|
441
453
|
const authModel = QueryUtils.getModel(this.schema, this.authType);
|
|
442
454
|
if (!authModel) {
|
|
443
|
-
throw
|
|
455
|
+
throw createUnsupportedError(`Unsupported use of \`auth()\` in policy of model "${context.model}", comparing with \`auth()\` is only possible when auth type is a model`);
|
|
444
456
|
}
|
|
445
457
|
const idFields = Object.values(authModel.fields).filter((f) => f.id).map((f) => f.name);
|
|
446
458
|
invariant2(idFields.length > 0, "auth type model must have at least one id field");
|
|
@@ -490,7 +502,7 @@ var ExpressionTransformer = class {
|
|
|
490
502
|
transformCall(expr2, context) {
|
|
491
503
|
const func = this.getFunctionImpl(expr2.function);
|
|
492
504
|
if (!func) {
|
|
493
|
-
throw
|
|
505
|
+
throw createUnsupportedError(`Function not implemented: ${expr2.function}`);
|
|
494
506
|
}
|
|
495
507
|
const eb = expressionBuilder();
|
|
496
508
|
return func(eb, (expr2.args ?? []).map((arg) => this.transformCallArg(eb, arg, context)), {
|
|
@@ -527,7 +539,7 @@ var ExpressionTransformer = class {
|
|
|
527
539
|
const valNode = this.valueMemberAccess(this.auth, arg, this.authType);
|
|
528
540
|
return valNode ? eb.val(valNode.value) : eb.val(null);
|
|
529
541
|
}
|
|
530
|
-
throw
|
|
542
|
+
throw createUnsupportedError(`Unsupported argument expression: ${arg.kind}`);
|
|
531
543
|
}
|
|
532
544
|
_member(expr2, context) {
|
|
533
545
|
if (this.isAuthCall(expr2.receiver)) {
|
|
@@ -799,7 +811,7 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
799
811
|
}
|
|
800
812
|
async handle(node, proceed) {
|
|
801
813
|
if (!this.isCrudQueryNode(node)) {
|
|
802
|
-
throw
|
|
814
|
+
throw createRejectedByPolicyError(void 0, RejectedByPolicyReason.OTHER, "non-CRUD queries are not allowed");
|
|
803
815
|
}
|
|
804
816
|
if (!this.isMutationQueryNode(node)) {
|
|
805
817
|
return proceed(this.transformNode(node));
|
|
@@ -813,7 +825,7 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
813
825
|
if (constCondition === true) {
|
|
814
826
|
needCheckPreCreate = false;
|
|
815
827
|
} else if (constCondition === false) {
|
|
816
|
-
throw
|
|
828
|
+
throw createRejectedByPolicyError(mutationModel, RejectedByPolicyReason.NO_ACCESS);
|
|
817
829
|
}
|
|
818
830
|
}
|
|
819
831
|
if (needCheckPreCreate) {
|
|
@@ -833,7 +845,7 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
833
845
|
for (const postRow of result.rows) {
|
|
834
846
|
const beforeRow = beforeUpdateInfo.rows.find((r) => idFields.every((f) => r[f] === postRow[f]));
|
|
835
847
|
if (!beforeRow) {
|
|
836
|
-
throw
|
|
848
|
+
throw createRejectedByPolicyError(mutationModel, RejectedByPolicyReason.OTHER, "Before-update and after-update rows do not match by id. If you have post-update policies on a model, updating id fields is not supported.");
|
|
837
849
|
}
|
|
838
850
|
}
|
|
839
851
|
}
|
|
@@ -864,7 +876,7 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
864
876
|
}));
|
|
865
877
|
const postUpdateResult = await proceed(postUpdateQuery.toOperationNode());
|
|
866
878
|
if (!postUpdateResult.rows[0]?.$condition) {
|
|
867
|
-
throw
|
|
879
|
+
throw createRejectedByPolicyError(mutationModel, RejectedByPolicyReason.NO_ACCESS, "some or all updated rows failed to pass post-update policy check");
|
|
868
880
|
}
|
|
869
881
|
}
|
|
870
882
|
if (!node.returning || this.onlyReturningId(node)) {
|
|
@@ -872,7 +884,7 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
872
884
|
} else {
|
|
873
885
|
const readBackResult = await this.processReadBack(node, result, proceed);
|
|
874
886
|
if (readBackResult.rows.length !== result.rows.length) {
|
|
875
|
-
throw
|
|
887
|
+
throw createRejectedByPolicyError(mutationModel, RejectedByPolicyReason.CANNOT_READ_BACK, "result is not allowed to be read back");
|
|
876
888
|
}
|
|
877
889
|
return readBackResult;
|
|
878
890
|
}
|
|
@@ -1131,10 +1143,10 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
1131
1143
|
};
|
|
1132
1144
|
const result = await proceed(queryNode);
|
|
1133
1145
|
if (!result.rows[0]?.$conditionA) {
|
|
1134
|
-
throw
|
|
1146
|
+
throw createRejectedByPolicyError(m2m.firstModel, RejectedByPolicyReason.CANNOT_READ_BACK, `many-to-many relation participant model "${m2m.firstModel}" not updatable`);
|
|
1135
1147
|
}
|
|
1136
1148
|
if (!result.rows[0]?.$conditionB) {
|
|
1137
|
-
throw
|
|
1149
|
+
throw createRejectedByPolicyError(m2m.secondModel, RejectedByPolicyReason.NO_ACCESS, `many-to-many relation participant model "${m2m.secondModel}" not updatable`);
|
|
1138
1150
|
}
|
|
1139
1151
|
}
|
|
1140
1152
|
async enforcePreCreatePolicyForOne(model, fields, values, proceed) {
|
|
@@ -1176,7 +1188,7 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
1176
1188
|
};
|
|
1177
1189
|
const result = await proceed(preCreateCheck);
|
|
1178
1190
|
if (!result.rows[0]?.$condition) {
|
|
1179
|
-
throw
|
|
1191
|
+
throw createRejectedByPolicyError(model, RejectedByPolicyReason.NO_ACCESS);
|
|
1180
1192
|
}
|
|
1181
1193
|
}
|
|
1182
1194
|
unwrapCreateValueRows(node, model, fields, isManyToManyJoinTable) {
|
|
@@ -1187,7 +1199,7 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
1187
1199
|
this.unwrapCreateValueRow(node.values, model, fields, isManyToManyJoinTable)
|
|
1188
1200
|
];
|
|
1189
1201
|
} else {
|
|
1190
|
-
|
|
1202
|
+
invariant3(false, `Unexpected node kind: ${node.kind} for unwrapping create values`);
|
|
1191
1203
|
}
|
|
1192
1204
|
}
|
|
1193
1205
|
unwrapCreateValueRow(data, model, fields, isImplicitManyToManyJoinTable) {
|
|
@@ -1278,7 +1290,7 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
1278
1290
|
alias: void 0
|
|
1279
1291
|
})).when(UpdateQueryNode.is, (node2) => {
|
|
1280
1292
|
if (!node2.table) {
|
|
1281
|
-
|
|
1293
|
+
invariant3(false, "Update query must have a table");
|
|
1282
1294
|
}
|
|
1283
1295
|
const r2 = this.extractTableName(node2.table);
|
|
1284
1296
|
return r2 ? {
|
|
@@ -1287,7 +1299,7 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
1287
1299
|
} : void 0;
|
|
1288
1300
|
}).when(DeleteQueryNode.is, (node2) => {
|
|
1289
1301
|
if (node2.from.froms.length !== 1) {
|
|
1290
|
-
throw
|
|
1302
|
+
throw createUnsupportedError("Only one from table is supported for delete");
|
|
1291
1303
|
}
|
|
1292
1304
|
const r2 = this.extractTableName(node2.from.froms[0]);
|
|
1293
1305
|
return r2 ? {
|
|
@@ -1296,7 +1308,7 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
1296
1308
|
} : void 0;
|
|
1297
1309
|
}).exhaustive();
|
|
1298
1310
|
if (!r) {
|
|
1299
|
-
|
|
1311
|
+
invariant3(false, `Unable to get table name for query node: ${node}`);
|
|
1300
1312
|
}
|
|
1301
1313
|
return r;
|
|
1302
1314
|
}
|