@zenstackhq/plugin-policy 3.0.0-beta.9 → 3.0.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/dist/index.cjs +218 -103
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +187 -72
- package/dist/index.js.map +1 -1
- package/package.json +13 -9
- package/plugin.zmodel +72 -0
package/dist/index.d.cts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import * as kysely from 'kysely';
|
|
2
|
-
import * as
|
|
3
|
-
import { RuntimePlugin, OnKyselyQueryArgs } from '@zenstackhq/
|
|
4
|
-
import { SchemaDef } from '@zenstackhq/
|
|
2
|
+
import * as _zenstackhq_orm from '@zenstackhq/orm';
|
|
3
|
+
import { RuntimePlugin, OnKyselyQueryArgs } from '@zenstackhq/orm';
|
|
4
|
+
import { SchemaDef } from '@zenstackhq/orm/schema';
|
|
5
5
|
|
|
6
6
|
declare class PolicyPlugin<Schema extends SchemaDef> implements RuntimePlugin<Schema> {
|
|
7
7
|
get id(): string;
|
|
8
8
|
get name(): string;
|
|
9
9
|
get description(): string;
|
|
10
10
|
get functions(): {
|
|
11
|
-
check:
|
|
11
|
+
check: _zenstackhq_orm.ZModelFunction<any>;
|
|
12
12
|
};
|
|
13
13
|
onKyselyQuery({ query, client, proceed }: OnKyselyQueryArgs<Schema>): Promise<kysely.QueryResult<any>>;
|
|
14
14
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import * as kysely from 'kysely';
|
|
2
|
-
import * as
|
|
3
|
-
import { RuntimePlugin, OnKyselyQueryArgs } from '@zenstackhq/
|
|
4
|
-
import { SchemaDef } from '@zenstackhq/
|
|
2
|
+
import * as _zenstackhq_orm from '@zenstackhq/orm';
|
|
3
|
+
import { RuntimePlugin, OnKyselyQueryArgs } from '@zenstackhq/orm';
|
|
4
|
+
import { SchemaDef } from '@zenstackhq/orm/schema';
|
|
5
5
|
|
|
6
6
|
declare class PolicyPlugin<Schema extends SchemaDef> implements RuntimePlugin<Schema> {
|
|
7
7
|
get id(): string;
|
|
8
8
|
get name(): string;
|
|
9
9
|
get description(): string;
|
|
10
10
|
get functions(): {
|
|
11
|
-
check:
|
|
11
|
+
check: _zenstackhq_orm.ZModelFunction<any>;
|
|
12
12
|
};
|
|
13
13
|
onKyselyQuery({ query, client, proceed }: OnKyselyQueryArgs<Schema>): Promise<kysely.QueryResult<any>>;
|
|
14
14
|
}
|
package/dist/index.js
CHANGED
|
@@ -3,20 +3,19 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
|
|
|
3
3
|
|
|
4
4
|
// src/functions.ts
|
|
5
5
|
import { invariant as invariant4 } from "@zenstackhq/common-helpers";
|
|
6
|
-
import { CRUD, QueryUtils as QueryUtils3 } from "@zenstackhq/
|
|
6
|
+
import { CRUD, QueryUtils as QueryUtils3 } from "@zenstackhq/orm";
|
|
7
7
|
import { ExpressionWrapper as ExpressionWrapper2, ValueNode as ValueNode4 } from "kysely";
|
|
8
8
|
|
|
9
9
|
// src/policy-handler.ts
|
|
10
10
|
import { invariant as invariant3 } from "@zenstackhq/common-helpers";
|
|
11
|
-
import { getCrudDialect as getCrudDialect2,
|
|
12
|
-
import { ExpressionUtils as ExpressionUtils4 } from "@zenstackhq/
|
|
13
|
-
import { ExpressionVisitor } from "@zenstackhq/sdk";
|
|
11
|
+
import { getCrudDialect as getCrudDialect2, QueryUtils as QueryUtils2, RejectedByPolicyReason, SchemaUtils as SchemaUtils2 } from "@zenstackhq/orm";
|
|
12
|
+
import { ExpressionUtils as ExpressionUtils4 } from "@zenstackhq/orm/schema";
|
|
14
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";
|
|
15
14
|
import { match as match3 } from "ts-pattern";
|
|
16
15
|
|
|
17
16
|
// src/column-collector.ts
|
|
18
|
-
import {
|
|
19
|
-
var ColumnCollector = class extends DefaultOperationNodeVisitor {
|
|
17
|
+
import { KyselyUtils } from "@zenstackhq/orm";
|
|
18
|
+
var ColumnCollector = class extends KyselyUtils.DefaultOperationNodeVisitor {
|
|
20
19
|
static {
|
|
21
20
|
__name(this, "ColumnCollector");
|
|
22
21
|
}
|
|
@@ -35,15 +34,15 @@ var ColumnCollector = class extends DefaultOperationNodeVisitor {
|
|
|
35
34
|
|
|
36
35
|
// src/expression-transformer.ts
|
|
37
36
|
import { invariant as invariant2 } from "@zenstackhq/common-helpers";
|
|
38
|
-
import { getCrudDialect,
|
|
39
|
-
import { ExpressionUtils as ExpressionUtils3 } from "@zenstackhq/
|
|
37
|
+
import { getCrudDialect, QueryUtils, SchemaUtils } from "@zenstackhq/orm";
|
|
38
|
+
import { ExpressionUtils as ExpressionUtils3 } from "@zenstackhq/orm/schema";
|
|
40
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";
|
|
41
40
|
import { match as match2 } from "ts-pattern";
|
|
42
41
|
|
|
43
42
|
// src/expression-evaluator.ts
|
|
44
43
|
import { invariant } from "@zenstackhq/common-helpers";
|
|
45
44
|
import { match } from "ts-pattern";
|
|
46
|
-
import { ExpressionUtils } from "@zenstackhq/
|
|
45
|
+
import { ExpressionUtils } from "@zenstackhq/orm/schema";
|
|
47
46
|
var ExpressionEvaluator = class {
|
|
48
47
|
static {
|
|
49
48
|
__name(this, "ExpressionEvaluator");
|
|
@@ -84,6 +83,12 @@ var ExpressionEvaluator = class {
|
|
|
84
83
|
}
|
|
85
84
|
const left = this.evaluate(expr2.left, context);
|
|
86
85
|
const right = this.evaluate(expr2.right, context);
|
|
86
|
+
if (![
|
|
87
|
+
"==",
|
|
88
|
+
"!="
|
|
89
|
+
].includes(expr2.op) && (left === null || right === null)) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
87
92
|
return match(expr2.op).with("==", () => left === right).with("!=", () => left !== right).with(">", () => left > right).with(">=", () => left >= right).with("<", () => left < right).with("<=", () => left <= right).with("&&", () => left && right).with("||", () => left || right).with("in", () => {
|
|
88
93
|
const _right = right ?? [];
|
|
89
94
|
invariant(Array.isArray(_right), 'expected array for "in" operator');
|
|
@@ -94,8 +99,8 @@ var ExpressionEvaluator = class {
|
|
|
94
99
|
const op = expr2.op;
|
|
95
100
|
invariant(op === "?" || op === "!" || op === "^", 'expected "?" or "!" or "^" operator');
|
|
96
101
|
const left = this.evaluate(expr2.left, context);
|
|
97
|
-
if (
|
|
98
|
-
return
|
|
102
|
+
if (left === null || left === void 0) {
|
|
103
|
+
return null;
|
|
99
104
|
}
|
|
100
105
|
invariant(Array.isArray(left), "expected array");
|
|
101
106
|
return match(op).with("?", () => left.some((item) => this.evaluate(expr2.right, {
|
|
@@ -111,8 +116,16 @@ var ExpressionEvaluator = class {
|
|
|
111
116
|
}
|
|
112
117
|
};
|
|
113
118
|
|
|
119
|
+
// src/types.ts
|
|
120
|
+
var CollectionPredicateOperator = [
|
|
121
|
+
"?",
|
|
122
|
+
"!",
|
|
123
|
+
"^"
|
|
124
|
+
];
|
|
125
|
+
|
|
114
126
|
// src/utils.ts
|
|
115
|
-
import {
|
|
127
|
+
import { ORMError, ORMErrorReason } from "@zenstackhq/orm";
|
|
128
|
+
import { ExpressionUtils as ExpressionUtils2 } from "@zenstackhq/orm/schema";
|
|
116
129
|
import { AliasNode, AndNode, BinaryOperationNode, FunctionNode, OperatorNode, OrNode, ParensNode, ReferenceNode, TableNode, UnaryOperationNode, ValueNode } from "kysely";
|
|
117
130
|
function trueNode(dialect) {
|
|
118
131
|
return ValueNode.createImmediate(dialect.transformPrimitive(true, "Boolean", false));
|
|
@@ -213,6 +226,17 @@ function isBeforeInvocation(expr2) {
|
|
|
213
226
|
return ExpressionUtils2.isCall(expr2) && expr2.function === "before";
|
|
214
227
|
}
|
|
215
228
|
__name(isBeforeInvocation, "isBeforeInvocation");
|
|
229
|
+
function createRejectedByPolicyError(model, reason, message) {
|
|
230
|
+
const err = new ORMError(ORMErrorReason.REJECTED_BY_POLICY, message ?? "operation is rejected by access policies");
|
|
231
|
+
err.rejectedByPolicyReason = reason;
|
|
232
|
+
err.model = model;
|
|
233
|
+
return err;
|
|
234
|
+
}
|
|
235
|
+
__name(createRejectedByPolicyError, "createRejectedByPolicyError");
|
|
236
|
+
function createUnsupportedError(message) {
|
|
237
|
+
return new ORMError(ORMErrorReason.NOT_SUPPORTED, message);
|
|
238
|
+
}
|
|
239
|
+
__name(createUnsupportedError, "createUnsupportedError");
|
|
216
240
|
|
|
217
241
|
// src/expression-transformer.ts
|
|
218
242
|
function _ts_decorate(decorators, target, key, desc) {
|
|
@@ -257,7 +281,7 @@ var ExpressionTransformer = class {
|
|
|
257
281
|
}
|
|
258
282
|
get authType() {
|
|
259
283
|
if (!this.schema.authType) {
|
|
260
|
-
|
|
284
|
+
invariant2(false, 'Schema does not have an "authType" specified');
|
|
261
285
|
}
|
|
262
286
|
return this.schema.authType;
|
|
263
287
|
}
|
|
@@ -275,7 +299,11 @@ var ExpressionTransformer = class {
|
|
|
275
299
|
return ValueListNode.create(expr2.items.map((item) => this.transform(item, context)));
|
|
276
300
|
}
|
|
277
301
|
_field(expr2, context) {
|
|
278
|
-
|
|
302
|
+
if (context.contextValue) {
|
|
303
|
+
const fieldDef2 = QueryUtils.requireField(this.schema, context.modelOrType, expr2.field);
|
|
304
|
+
return this.transformValue(context.contextValue[expr2.field], fieldDef2.type);
|
|
305
|
+
}
|
|
306
|
+
const fieldDef = QueryUtils.requireField(this.schema, context.modelOrType, expr2.field);
|
|
279
307
|
if (!fieldDef.relation) {
|
|
280
308
|
return this.createColumnRef(expr2.field, context);
|
|
281
309
|
} else {
|
|
@@ -349,30 +377,39 @@ var ExpressionTransformer = class {
|
|
|
349
377
|
}
|
|
350
378
|
}
|
|
351
379
|
transformNullCheck(expr2, operator) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
380
|
+
if (operator === "==" || operator === "!=") {
|
|
381
|
+
if (ValueNode2.is(expr2)) {
|
|
382
|
+
if (expr2.value === null) {
|
|
383
|
+
return operator === "==" ? trueNode(this.dialect) : falseNode(this.dialect);
|
|
384
|
+
} else {
|
|
385
|
+
return operator === "==" ? falseNode(this.dialect) : trueNode(this.dialect);
|
|
386
|
+
}
|
|
356
387
|
} else {
|
|
357
|
-
return operator === "==" ?
|
|
388
|
+
return operator === "==" ? BinaryOperationNode2.create(expr2, OperatorNode2.create("is"), ValueNode2.createImmediate(null)) : BinaryOperationNode2.create(expr2, OperatorNode2.create("is not"), ValueNode2.createImmediate(null));
|
|
358
389
|
}
|
|
359
390
|
} else {
|
|
360
|
-
return
|
|
391
|
+
return ValueNode2.createImmediate(null);
|
|
361
392
|
}
|
|
362
393
|
}
|
|
363
394
|
normalizeBinaryOperationOperands(expr2, context) {
|
|
395
|
+
if (context.contextValue) {
|
|
396
|
+
return {
|
|
397
|
+
normalizedLeft: expr2.left,
|
|
398
|
+
normalizedRight: expr2.right
|
|
399
|
+
};
|
|
400
|
+
}
|
|
364
401
|
let normalizedLeft = expr2.left;
|
|
365
|
-
if (this.isRelationField(expr2.left, context.
|
|
402
|
+
if (this.isRelationField(expr2.left, context.modelOrType)) {
|
|
366
403
|
invariant2(ExpressionUtils3.isNull(expr2.right), "only null comparison is supported for relation field");
|
|
367
|
-
const leftRelDef = this.getFieldDefFromFieldRef(expr2.left, context.
|
|
404
|
+
const leftRelDef = this.getFieldDefFromFieldRef(expr2.left, context.modelOrType);
|
|
368
405
|
invariant2(leftRelDef, "failed to get relation field definition");
|
|
369
406
|
const idFields = QueryUtils.requireIdFields(this.schema, leftRelDef.type);
|
|
370
407
|
normalizedLeft = this.makeOrAppendMember(normalizedLeft, idFields[0]);
|
|
371
408
|
}
|
|
372
409
|
let normalizedRight = expr2.right;
|
|
373
|
-
if (this.isRelationField(expr2.right, context.
|
|
410
|
+
if (this.isRelationField(expr2.right, context.modelOrType)) {
|
|
374
411
|
invariant2(ExpressionUtils3.isNull(expr2.left), "only null comparison is supported for relation field");
|
|
375
|
-
const rightRelDef = this.getFieldDefFromFieldRef(expr2.right, context.
|
|
412
|
+
const rightRelDef = this.getFieldDefFromFieldRef(expr2.right, context.modelOrType);
|
|
376
413
|
invariant2(rightRelDef, "failed to get relation field definition");
|
|
377
414
|
const idFields = QueryUtils.requireIdFields(this.schema, rightRelDef.type);
|
|
378
415
|
normalizedRight = this.makeOrAppendMember(normalizedRight, idFields[0]);
|
|
@@ -383,22 +420,30 @@ var ExpressionTransformer = class {
|
|
|
383
420
|
};
|
|
384
421
|
}
|
|
385
422
|
transformCollectionPredicate(expr2, context) {
|
|
386
|
-
|
|
387
|
-
if (this.
|
|
388
|
-
|
|
423
|
+
this.ensureCollectionPredicateOperator(expr2.op);
|
|
424
|
+
if (this.isAuthMember(expr2.left) || context.contextValue) {
|
|
425
|
+
invariant2(ExpressionUtils3.isMember(expr2.left) || ExpressionUtils3.isField(expr2.left), "expected member or field expression");
|
|
426
|
+
const evaluator = new ExpressionEvaluator();
|
|
427
|
+
const receiver = evaluator.evaluate(expr2.left, {
|
|
428
|
+
thisValue: context.contextValue,
|
|
389
429
|
auth: this.auth
|
|
390
430
|
});
|
|
391
|
-
|
|
431
|
+
const baseType = this.isAuthMember(expr2.left) ? this.authType : context.modelOrType;
|
|
432
|
+
const memberType = this.getMemberType(baseType, expr2.left);
|
|
433
|
+
return this.transformValueCollectionPredicate(receiver, expr2, {
|
|
434
|
+
...context,
|
|
435
|
+
modelOrType: memberType
|
|
436
|
+
});
|
|
392
437
|
}
|
|
393
438
|
invariant2(ExpressionUtils3.isField(expr2.left) || ExpressionUtils3.isMember(expr2.left), "left operand must be field or member access");
|
|
394
439
|
let newContextModel;
|
|
395
|
-
const fieldDef = this.getFieldDefFromFieldRef(expr2.left, context.
|
|
440
|
+
const fieldDef = this.getFieldDefFromFieldRef(expr2.left, context.modelOrType);
|
|
396
441
|
if (fieldDef) {
|
|
397
442
|
invariant2(fieldDef.relation, `field is not a relation: ${JSON.stringify(expr2.left)}`);
|
|
398
443
|
newContextModel = fieldDef.type;
|
|
399
444
|
} else {
|
|
400
445
|
invariant2(ExpressionUtils3.isMember(expr2.left) && ExpressionUtils3.isField(expr2.left.receiver), "left operand must be member access with field receiver");
|
|
401
|
-
const fieldDef2 = QueryUtils.requireField(this.schema, context.
|
|
446
|
+
const fieldDef2 = QueryUtils.requireField(this.schema, context.modelOrType, expr2.left.receiver.field);
|
|
402
447
|
newContextModel = fieldDef2.type;
|
|
403
448
|
for (const member of expr2.left.members) {
|
|
404
449
|
const memberDef = QueryUtils.requireField(this.schema, newContextModel, member);
|
|
@@ -407,7 +452,7 @@ var ExpressionTransformer = class {
|
|
|
407
452
|
}
|
|
408
453
|
let predicateFilter = this.transform(expr2.right, {
|
|
409
454
|
...context,
|
|
410
|
-
|
|
455
|
+
modelOrType: newContextModel,
|
|
411
456
|
alias: void 0
|
|
412
457
|
});
|
|
413
458
|
if (expr2.op === "!") {
|
|
@@ -423,9 +468,49 @@ var ExpressionTransformer = class {
|
|
|
423
468
|
memberFilter: predicateFilter
|
|
424
469
|
});
|
|
425
470
|
}
|
|
471
|
+
ensureCollectionPredicateOperator(op) {
|
|
472
|
+
invariant2(CollectionPredicateOperator.includes(op), 'expected "?" or "!" or "^" operator');
|
|
473
|
+
}
|
|
474
|
+
transformValueCollectionPredicate(receiver, expr2, context) {
|
|
475
|
+
if (!receiver) {
|
|
476
|
+
return ValueNode2.createImmediate(null);
|
|
477
|
+
}
|
|
478
|
+
this.ensureCollectionPredicateOperator(expr2.op);
|
|
479
|
+
const visitor = new SchemaUtils.MatchingExpressionVisitor((e) => ExpressionUtils3.isThis(e));
|
|
480
|
+
if (!visitor.find(expr2.right)) {
|
|
481
|
+
const value = new ExpressionEvaluator().evaluate(expr2, {
|
|
482
|
+
auth: this.auth,
|
|
483
|
+
thisValue: context.contextValue
|
|
484
|
+
});
|
|
485
|
+
return this.transformValue(value, "Boolean");
|
|
486
|
+
} else {
|
|
487
|
+
invariant2(Array.isArray(receiver), "array value is expected");
|
|
488
|
+
const components = receiver.map((item) => this.transform(expr2.right, {
|
|
489
|
+
operation: context.operation,
|
|
490
|
+
thisType: context.thisType,
|
|
491
|
+
thisAlias: context.thisAlias,
|
|
492
|
+
modelOrType: context.modelOrType,
|
|
493
|
+
contextValue: item
|
|
494
|
+
}));
|
|
495
|
+
return match2(expr2.op).with("?", () => disjunction(this.dialect, components)).with("!", () => conjunction(this.dialect, components)).with("^", () => logicalNot(this.dialect, disjunction(this.dialect, components))).exhaustive();
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
getMemberType(receiverType, expr2) {
|
|
499
|
+
if (ExpressionUtils3.isField(expr2)) {
|
|
500
|
+
const fieldDef = QueryUtils.requireField(this.schema, receiverType, expr2.field);
|
|
501
|
+
return fieldDef.type;
|
|
502
|
+
} else {
|
|
503
|
+
let currType = receiverType;
|
|
504
|
+
for (const member of expr2.members) {
|
|
505
|
+
const fieldDef = QueryUtils.requireField(this.schema, currType, member);
|
|
506
|
+
currType = fieldDef.type;
|
|
507
|
+
}
|
|
508
|
+
return currType;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
426
511
|
transformAuthBinary(expr2, context) {
|
|
427
512
|
if (expr2.op !== "==" && expr2.op !== "!=") {
|
|
428
|
-
throw
|
|
513
|
+
throw createUnsupportedError(`Unsupported operator for \`auth()\` in policy of model "${context.modelOrType}": ${expr2.op}`);
|
|
429
514
|
}
|
|
430
515
|
let authExpr;
|
|
431
516
|
let other;
|
|
@@ -441,7 +526,7 @@ var ExpressionTransformer = class {
|
|
|
441
526
|
} else {
|
|
442
527
|
const authModel = QueryUtils.getModel(this.schema, this.authType);
|
|
443
528
|
if (!authModel) {
|
|
444
|
-
throw
|
|
529
|
+
throw createUnsupportedError(`Unsupported use of \`auth()\` in policy of model "${context.modelOrType}", comparing with \`auth()\` is only possible when auth type is a model`);
|
|
445
530
|
}
|
|
446
531
|
const idFields = Object.values(authModel.fields).filter((f) => f.id).map((f) => f.name);
|
|
447
532
|
invariant2(idFields.length > 0, "auth type model must have at least one id field");
|
|
@@ -473,7 +558,12 @@ var ExpressionTransformer = class {
|
|
|
473
558
|
} else if (value === false) {
|
|
474
559
|
return falseNode(this.dialect);
|
|
475
560
|
} else {
|
|
476
|
-
|
|
561
|
+
const transformed = this.dialect.transformPrimitive(value, type, false) ?? null;
|
|
562
|
+
if (!Array.isArray(transformed)) {
|
|
563
|
+
return ValueNode2.createImmediate(transformed);
|
|
564
|
+
} else {
|
|
565
|
+
return ValueNode2.create(transformed);
|
|
566
|
+
}
|
|
477
567
|
}
|
|
478
568
|
}
|
|
479
569
|
_unary(expr2, context) {
|
|
@@ -491,14 +581,14 @@ var ExpressionTransformer = class {
|
|
|
491
581
|
transformCall(expr2, context) {
|
|
492
582
|
const func = this.getFunctionImpl(expr2.function);
|
|
493
583
|
if (!func) {
|
|
494
|
-
throw
|
|
584
|
+
throw createUnsupportedError(`Function not implemented: ${expr2.function}`);
|
|
495
585
|
}
|
|
496
586
|
const eb = expressionBuilder();
|
|
497
587
|
return func(eb, (expr2.args ?? []).map((arg) => this.transformCallArg(eb, arg, context)), {
|
|
498
588
|
client: this.client,
|
|
499
589
|
dialect: this.dialect,
|
|
500
|
-
model: context.
|
|
501
|
-
modelAlias: context.alias ?? context.
|
|
590
|
+
model: context.modelOrType,
|
|
591
|
+
modelAlias: context.alias ?? context.modelOrType,
|
|
502
592
|
operation: context.operation
|
|
503
593
|
});
|
|
504
594
|
}
|
|
@@ -528,7 +618,7 @@ var ExpressionTransformer = class {
|
|
|
528
618
|
const valNode = this.valueMemberAccess(this.auth, arg, this.authType);
|
|
529
619
|
return valNode ? eb.val(valNode.value) : eb.val(null);
|
|
530
620
|
}
|
|
531
|
-
throw
|
|
621
|
+
throw createUnsupportedError(`Unsupported argument expression: ${arg.kind}`);
|
|
532
622
|
}
|
|
533
623
|
_member(expr2, context) {
|
|
534
624
|
if (this.isAuthCall(expr2.receiver)) {
|
|
@@ -545,9 +635,15 @@ var ExpressionTransformer = class {
|
|
|
545
635
|
const { memberFilter, memberSelect, ...restContext } = context;
|
|
546
636
|
if (ExpressionUtils3.isThis(expr2.receiver)) {
|
|
547
637
|
if (expr2.members.length === 1) {
|
|
548
|
-
return this._field(ExpressionUtils3.field(expr2.members[0]),
|
|
638
|
+
return this._field(ExpressionUtils3.field(expr2.members[0]), {
|
|
639
|
+
...context,
|
|
640
|
+
alias: context.thisAlias,
|
|
641
|
+
modelOrType: context.thisType,
|
|
642
|
+
thisType: context.thisType,
|
|
643
|
+
contextValue: void 0
|
|
644
|
+
});
|
|
549
645
|
} else {
|
|
550
|
-
const firstMemberFieldDef = QueryUtils.requireField(this.schema, context.
|
|
646
|
+
const firstMemberFieldDef = QueryUtils.requireField(this.schema, context.thisType, expr2.members[0]);
|
|
551
647
|
receiver = this.transformRelationAccess(expr2.members[0], firstMemberFieldDef.type, restContext);
|
|
552
648
|
members = expr2.members.slice(1);
|
|
553
649
|
}
|
|
@@ -557,10 +653,10 @@ var ExpressionTransformer = class {
|
|
|
557
653
|
invariant2(SelectQueryNode.is(receiver), "expected receiver to be select query");
|
|
558
654
|
let startType;
|
|
559
655
|
if (ExpressionUtils3.isField(expr2.receiver)) {
|
|
560
|
-
const receiverField = QueryUtils.requireField(this.schema, context.
|
|
656
|
+
const receiverField = QueryUtils.requireField(this.schema, context.modelOrType, expr2.receiver.field);
|
|
561
657
|
startType = receiverField.type;
|
|
562
658
|
} else {
|
|
563
|
-
startType = context.
|
|
659
|
+
startType = context.thisType;
|
|
564
660
|
}
|
|
565
661
|
const memberFields = [];
|
|
566
662
|
let currType = startType;
|
|
@@ -579,7 +675,7 @@ var ExpressionTransformer = class {
|
|
|
579
675
|
if (fieldDef.relation) {
|
|
580
676
|
const relation = this.transformRelationAccess(member, fieldDef.type, {
|
|
581
677
|
...restContext,
|
|
582
|
-
|
|
678
|
+
modelOrType: fromModel,
|
|
583
679
|
alias: void 0
|
|
584
680
|
});
|
|
585
681
|
if (currNode) {
|
|
@@ -615,20 +711,29 @@ var ExpressionTransformer = class {
|
|
|
615
711
|
if (!receiver) {
|
|
616
712
|
return ValueNode2.createImmediate(null);
|
|
617
713
|
}
|
|
618
|
-
|
|
619
|
-
|
|
714
|
+
invariant2(expr2.members.length > 0, "member expression must have at least one member");
|
|
715
|
+
let curr = receiver;
|
|
716
|
+
let currType = receiverType;
|
|
717
|
+
for (let i = 0; i < expr2.members.length; i++) {
|
|
718
|
+
const field = expr2.members[i];
|
|
719
|
+
curr = curr?.[field];
|
|
720
|
+
if (curr === void 0) {
|
|
721
|
+
curr = ValueNode2.createImmediate(null);
|
|
722
|
+
break;
|
|
723
|
+
}
|
|
724
|
+
currType = QueryUtils.requireField(this.schema, currType, field).type;
|
|
725
|
+
if (i === expr2.members.length - 1) {
|
|
726
|
+
curr = this.transformValue(curr, currType);
|
|
727
|
+
}
|
|
620
728
|
}
|
|
621
|
-
|
|
622
|
-
const fieldDef = QueryUtils.requireField(this.schema, receiverType, field);
|
|
623
|
-
const fieldValue = receiver[field] ?? null;
|
|
624
|
-
return this.transformValue(fieldValue, fieldDef.type);
|
|
729
|
+
return curr;
|
|
625
730
|
}
|
|
626
731
|
transformRelationAccess(field, relationModel, context) {
|
|
627
|
-
const m2m = QueryUtils.getManyToManyRelation(this.schema, context.
|
|
732
|
+
const m2m = QueryUtils.getManyToManyRelation(this.schema, context.modelOrType, field);
|
|
628
733
|
if (m2m) {
|
|
629
734
|
return this.transformManyToManyRelationAccess(m2m, context);
|
|
630
735
|
}
|
|
631
|
-
const fromModel = context.
|
|
736
|
+
const fromModel = context.modelOrType;
|
|
632
737
|
const relationFieldDef = QueryUtils.requireField(this.schema, fromModel, field);
|
|
633
738
|
const { keyPairs, ownedByModel } = QueryUtils.getRelationForeignKeyFieldPairs(this.schema, fromModel, field);
|
|
634
739
|
let condition;
|
|
@@ -653,19 +758,19 @@ var ExpressionTransformer = class {
|
|
|
653
758
|
}
|
|
654
759
|
transformManyToManyRelationAccess(m2m, context) {
|
|
655
760
|
const eb = expressionBuilder();
|
|
656
|
-
const relationQuery = eb.selectFrom(m2m.otherModel).innerJoin(m2m.joinTable, (join) => join.onRef(`${m2m.otherModel}.${m2m.otherPKName}`, "=", `${m2m.joinTable}.${m2m.otherFkName}`).onRef(`${m2m.joinTable}.${m2m.parentFkName}`, "=", `${context.alias ?? context.
|
|
761
|
+
const relationQuery = eb.selectFrom(m2m.otherModel).innerJoin(m2m.joinTable, (join) => join.onRef(`${m2m.otherModel}.${m2m.otherPKName}`, "=", `${m2m.joinTable}.${m2m.otherFkName}`).onRef(`${m2m.joinTable}.${m2m.parentFkName}`, "=", `${context.alias ?? context.modelOrType}.${m2m.parentPKName}`));
|
|
657
762
|
return relationQuery.toOperationNode();
|
|
658
763
|
}
|
|
659
764
|
createColumnRef(column, context) {
|
|
660
|
-
const tableName = context.alias ?? context.
|
|
765
|
+
const tableName = context.alias ?? context.modelOrType;
|
|
661
766
|
if (context.operation === "create") {
|
|
662
767
|
return ReferenceNode2.create(ColumnNode.create(column), TableNode2.create(tableName));
|
|
663
768
|
}
|
|
664
|
-
const fieldDef = QueryUtils.requireField(this.schema, context.
|
|
665
|
-
if (!fieldDef.originModel || fieldDef.originModel === context.
|
|
769
|
+
const fieldDef = QueryUtils.requireField(this.schema, context.modelOrType, column);
|
|
770
|
+
if (!fieldDef.originModel || fieldDef.originModel === context.modelOrType) {
|
|
666
771
|
return ReferenceNode2.create(ColumnNode.create(column), TableNode2.create(tableName));
|
|
667
772
|
}
|
|
668
|
-
return this.buildDelegateBaseFieldSelect(context.
|
|
773
|
+
return this.buildDelegateBaseFieldSelect(context.modelOrType, tableName, column, fieldDef.originModel);
|
|
669
774
|
}
|
|
670
775
|
buildDelegateBaseFieldSelect(model, modelAlias, field, baseModel) {
|
|
671
776
|
const idFields = QueryUtils.requireIdFields(this.client.$schema, model);
|
|
@@ -707,9 +812,9 @@ var ExpressionTransformer = class {
|
|
|
707
812
|
}
|
|
708
813
|
getFieldDefFromFieldRef(expr2, model) {
|
|
709
814
|
if (ExpressionUtils3.isField(expr2)) {
|
|
710
|
-
return QueryUtils.
|
|
815
|
+
return QueryUtils.getField(this.schema, model, expr2.field);
|
|
711
816
|
} else if (ExpressionUtils3.isMember(expr2) && expr2.members.length === 1 && ExpressionUtils3.isThis(expr2.receiver)) {
|
|
712
|
-
return QueryUtils.
|
|
817
|
+
return QueryUtils.getField(this.schema, model, expr2.members[0]);
|
|
713
818
|
} else {
|
|
714
819
|
return void 0;
|
|
715
820
|
}
|
|
@@ -800,12 +905,13 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
800
905
|
}
|
|
801
906
|
async handle(node, proceed) {
|
|
802
907
|
if (!this.isCrudQueryNode(node)) {
|
|
803
|
-
throw
|
|
908
|
+
throw createRejectedByPolicyError(void 0, RejectedByPolicyReason.OTHER, "non-CRUD queries are not allowed");
|
|
804
909
|
}
|
|
805
910
|
if (!this.isMutationQueryNode(node)) {
|
|
806
911
|
return proceed(this.transformNode(node));
|
|
807
912
|
}
|
|
808
913
|
const { mutationModel } = this.getMutationModel(node);
|
|
914
|
+
this.tryRejectNonexistentModel(mutationModel);
|
|
809
915
|
if (InsertQueryNode.is(node)) {
|
|
810
916
|
const isManyToManyJoinTable = this.isManyToManyJoinTable(mutationModel);
|
|
811
917
|
let needCheckPreCreate = true;
|
|
@@ -814,7 +920,7 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
814
920
|
if (constCondition === true) {
|
|
815
921
|
needCheckPreCreate = false;
|
|
816
922
|
} else if (constCondition === false) {
|
|
817
|
-
throw
|
|
923
|
+
throw createRejectedByPolicyError(mutationModel, RejectedByPolicyReason.NO_ACCESS);
|
|
818
924
|
}
|
|
819
925
|
}
|
|
820
926
|
if (needCheckPreCreate) {
|
|
@@ -834,7 +940,7 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
834
940
|
for (const postRow of result.rows) {
|
|
835
941
|
const beforeRow = beforeUpdateInfo.rows.find((r) => idFields.every((f) => r[f] === postRow[f]));
|
|
836
942
|
if (!beforeRow) {
|
|
837
|
-
throw
|
|
943
|
+
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.");
|
|
838
944
|
}
|
|
839
945
|
}
|
|
840
946
|
}
|
|
@@ -865,7 +971,7 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
865
971
|
}));
|
|
866
972
|
const postUpdateResult = await proceed(postUpdateQuery.toOperationNode());
|
|
867
973
|
if (!postUpdateResult.rows[0]?.$condition) {
|
|
868
|
-
throw
|
|
974
|
+
throw createRejectedByPolicyError(mutationModel, RejectedByPolicyReason.NO_ACCESS, "some or all updated rows failed to pass post-update policy check");
|
|
869
975
|
}
|
|
870
976
|
}
|
|
871
977
|
if (!node.returning || this.onlyReturningId(node)) {
|
|
@@ -873,7 +979,7 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
873
979
|
} else {
|
|
874
980
|
const readBackResult = await this.processReadBack(node, result, proceed);
|
|
875
981
|
if (readBackResult.rows.length !== result.rows.length) {
|
|
876
|
-
throw
|
|
982
|
+
throw createRejectedByPolicyError(mutationModel, RejectedByPolicyReason.CANNOT_READ_BACK, "result is not allowed to be read back");
|
|
877
983
|
}
|
|
878
984
|
return readBackResult;
|
|
879
985
|
}
|
|
@@ -927,7 +1033,7 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
927
1033
|
return void 0;
|
|
928
1034
|
}
|
|
929
1035
|
const fields = /* @__PURE__ */ new Set();
|
|
930
|
-
const fieldCollector = new class extends ExpressionVisitor {
|
|
1036
|
+
const fieldCollector = new class extends SchemaUtils2.ExpressionVisitor {
|
|
931
1037
|
visitMember(e) {
|
|
932
1038
|
if (isBeforeInvocation(e.receiver)) {
|
|
933
1039
|
invariant3(e.members.length === 1, "before() can only be followed by a scalar field access");
|
|
@@ -972,6 +1078,7 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
972
1078
|
if (!table) {
|
|
973
1079
|
return super.transformJoin(node);
|
|
974
1080
|
}
|
|
1081
|
+
this.tryRejectNonexistentModel(table.model);
|
|
975
1082
|
const filter = this.buildPolicyFilter(table.model, table.alias, "read");
|
|
976
1083
|
const nestedSelect = {
|
|
977
1084
|
kind: "SelectQueryNode",
|
|
@@ -1132,10 +1239,10 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
1132
1239
|
};
|
|
1133
1240
|
const result = await proceed(queryNode);
|
|
1134
1241
|
if (!result.rows[0]?.$conditionA) {
|
|
1135
|
-
throw
|
|
1242
|
+
throw createRejectedByPolicyError(m2m.firstModel, RejectedByPolicyReason.CANNOT_READ_BACK, `many-to-many relation participant model "${m2m.firstModel}" not updatable`);
|
|
1136
1243
|
}
|
|
1137
1244
|
if (!result.rows[0]?.$conditionB) {
|
|
1138
|
-
throw
|
|
1245
|
+
throw createRejectedByPolicyError(m2m.secondModel, RejectedByPolicyReason.NO_ACCESS, `many-to-many relation participant model "${m2m.secondModel}" not updatable`);
|
|
1139
1246
|
}
|
|
1140
1247
|
}
|
|
1141
1248
|
async enforcePreCreatePolicyForOne(model, fields, values, proceed) {
|
|
@@ -1177,7 +1284,7 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
1177
1284
|
};
|
|
1178
1285
|
const result = await proceed(preCreateCheck);
|
|
1179
1286
|
if (!result.rows[0]?.$condition) {
|
|
1180
|
-
throw
|
|
1287
|
+
throw createRejectedByPolicyError(model, RejectedByPolicyReason.NO_ACCESS);
|
|
1181
1288
|
}
|
|
1182
1289
|
}
|
|
1183
1290
|
unwrapCreateValueRows(node, model, fields, isManyToManyJoinTable) {
|
|
@@ -1188,7 +1295,7 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
1188
1295
|
this.unwrapCreateValueRow(node.values, model, fields, isManyToManyJoinTable)
|
|
1189
1296
|
];
|
|
1190
1297
|
} else {
|
|
1191
|
-
|
|
1298
|
+
invariant3(false, `Unexpected node kind: ${node.kind} for unwrapping create values`);
|
|
1192
1299
|
}
|
|
1193
1300
|
}
|
|
1194
1301
|
unwrapCreateValueRow(data, model, fields, isImplicitManyToManyJoinTable) {
|
|
@@ -1279,7 +1386,7 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
1279
1386
|
alias: void 0
|
|
1280
1387
|
})).when(UpdateQueryNode.is, (node2) => {
|
|
1281
1388
|
if (!node2.table) {
|
|
1282
|
-
|
|
1389
|
+
invariant3(false, "Update query must have a table");
|
|
1283
1390
|
}
|
|
1284
1391
|
const r2 = this.extractTableName(node2.table);
|
|
1285
1392
|
return r2 ? {
|
|
@@ -1288,7 +1395,7 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
1288
1395
|
} : void 0;
|
|
1289
1396
|
}).when(DeleteQueryNode.is, (node2) => {
|
|
1290
1397
|
if (node2.from.froms.length !== 1) {
|
|
1291
|
-
throw
|
|
1398
|
+
throw createUnsupportedError("Only one from table is supported for delete");
|
|
1292
1399
|
}
|
|
1293
1400
|
const r2 = this.extractTableName(node2.from.froms[0]);
|
|
1294
1401
|
return r2 ? {
|
|
@@ -1297,7 +1404,7 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
1297
1404
|
} : void 0;
|
|
1298
1405
|
}).exhaustive();
|
|
1299
1406
|
if (!r) {
|
|
1300
|
-
|
|
1407
|
+
invariant3(false, `Unable to get table name for query node: ${node}`);
|
|
1301
1408
|
}
|
|
1302
1409
|
return r;
|
|
1303
1410
|
}
|
|
@@ -1364,6 +1471,7 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
1364
1471
|
const extractResult = this.extractTableName(table);
|
|
1365
1472
|
if (extractResult) {
|
|
1366
1473
|
const { model, alias } = extractResult;
|
|
1474
|
+
this.tryRejectNonexistentModel(model);
|
|
1367
1475
|
const filter = this.buildPolicyFilter(model, alias, "read");
|
|
1368
1476
|
return acc ? conjunction(this.dialect, [
|
|
1369
1477
|
acc,
|
|
@@ -1375,7 +1483,9 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
1375
1483
|
}
|
|
1376
1484
|
compilePolicyCondition(model, alias, operation, policy) {
|
|
1377
1485
|
return new ExpressionTransformer(this.client).transform(policy.condition, {
|
|
1378
|
-
model,
|
|
1486
|
+
modelOrType: model,
|
|
1487
|
+
thisType: model,
|
|
1488
|
+
thisAlias: alias,
|
|
1379
1489
|
alias,
|
|
1380
1490
|
operation
|
|
1381
1491
|
});
|
|
@@ -1449,6 +1559,11 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
1449
1559
|
bQuery
|
|
1450
1560
|
]).toOperationNode();
|
|
1451
1561
|
}
|
|
1562
|
+
tryRejectNonexistentModel(model) {
|
|
1563
|
+
if (!QueryUtils2.hasModel(this.client.$schema, model) && !this.isManyToManyJoinTable(model)) {
|
|
1564
|
+
throw createRejectedByPolicyError(model, RejectedByPolicyReason.NO_ACCESS);
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1452
1567
|
};
|
|
1453
1568
|
|
|
1454
1569
|
// src/functions.ts
|