@zenstackhq/runtime
Advanced tools
Comparing version 2.0.3 to 2.1.0
@@ -84,2 +84,7 @@ "use strict"; | ||
Object.entries(where).forEach(([field, value]) => { | ||
if (['AND', 'OR', 'NOT'].includes(field)) { | ||
// recurse into logical group | ||
(0, cross_1.enumerate)(value).forEach((item) => this.injectWhereHierarchy(model, item)); | ||
return; | ||
} | ||
const fieldInfo = (0, cross_1.resolveField)(this.options.modelMeta, model, field); | ||
@@ -86,0 +91,0 @@ if (!(fieldInfo === null || fieldInfo === void 0 ? void 0 : fieldInfo.inheritedFrom)) { |
@@ -1,4 +0,8 @@ | ||
import { type DbClientContract } from '../../types'; | ||
import { PolicyCrudKind, type DbClientContract } from '../../types'; | ||
import type { EnhancementContext, InternalEnhancementOptions } from '../create-enhancement'; | ||
import { PrismaProxyHandler } from '../proxy'; | ||
type PermissionCheckArgs = { | ||
operation: PolicyCrudKind; | ||
where?: Record<string, number | string | boolean>; | ||
}; | ||
/** | ||
@@ -55,2 +59,9 @@ * Prisma proxy handler for injecting access policy check. | ||
subscribe(args: any): Promise<any>; | ||
/** | ||
* Checks if the given operation is possibly allowed by the policy, without querying the database. | ||
* @param operation The CRUD operation. | ||
* @param fieldValues Extra field value filters to be combined with the policy constraints. | ||
*/ | ||
check(args: PermissionCheckArgs): Promise<boolean>; | ||
private doCheck; | ||
private get shouldLogQuery(); | ||
@@ -62,1 +73,2 @@ private runPostWriteChecks; | ||
} | ||
export {}; |
import { ZodError } from 'zod'; | ||
import { CrudFailureReason } from '../../constants'; | ||
import { CrudContract, DbClientContract, PolicyOperationKind } from '../../types'; | ||
import { CrudContract, DbClientContract, PolicyCrudKind, PolicyOperationKind } from '../../types'; | ||
import type { EnhancementContext, InternalEnhancementOptions } from '../create-enhancement'; | ||
import { QueryUtils } from '../query-utils'; | ||
import type { CheckerFunc } from '../types'; | ||
/** | ||
@@ -83,2 +84,7 @@ * Access policy enforcement utilities | ||
/** | ||
* Gets checker constraints for the given model and operation. | ||
*/ | ||
getCheckerConstraint(model: string, operation: PolicyCrudKind): ReturnType<CheckerFunc> | boolean; | ||
private getModelChecker; | ||
/** | ||
* Gets unique constraints for the given model. | ||
@@ -85,0 +91,0 @@ */ |
@@ -36,3 +36,3 @@ "use strict"; | ||
//#endregion | ||
//# Auth guard | ||
//#region Auth guard | ||
this.FULLY_OPEN_AUTH_GUARD = { | ||
@@ -244,3 +244,3 @@ create: true, | ||
if (!provider) { | ||
throw this.unknownError(`zenstack: unable to load authorization guard for ${model}`); | ||
throw this.unknownError(`unable to load authorization guard for ${model}`); | ||
} | ||
@@ -486,3 +486,36 @@ const r = provider({ user: this.user, preValue }, db); | ||
} | ||
//#endregion | ||
//#region Checker | ||
/** | ||
* Gets checker constraints for the given model and operation. | ||
*/ | ||
getCheckerConstraint(model, operation) { | ||
const checker = this.getModelChecker(model); | ||
const provider = checker[operation]; | ||
if (typeof provider === 'boolean') { | ||
return provider; | ||
} | ||
if (typeof provider !== 'function') { | ||
throw this.unknownError(`invalid ${operation} checker function for ${model}`); | ||
} | ||
// call checker function | ||
return provider({ user: this.user }); | ||
} | ||
getModelChecker(model) { | ||
var _a; | ||
if (this.options.kinds && !this.options.kinds.includes('policy')) { | ||
// policy enhancement not enabled, return a constant true checker | ||
return { create: true, read: true, update: true, delete: true }; | ||
} | ||
else { | ||
const result = (_a = this.options.policy.checker) === null || _a === void 0 ? void 0 : _a[(0, lower_case_first_1.lowerCaseFirst)(model)]; | ||
if (!result) { | ||
// checker generation not enabled, return constant false checker | ||
throw new Error(`Generated permission checkers not found. Please make sure the "generatePermissionChecker" option is set to true in the "@core/enhancer" plugin.`); | ||
} | ||
return result; | ||
} | ||
} | ||
//#endregion | ||
/** | ||
* Gets unique constraints for the given model. | ||
@@ -532,2 +565,5 @@ */ | ||
for (const field of (0, cross_1.getModelFields)(injectTarget)) { | ||
if (injectTarget[field] === false) { | ||
continue; | ||
} | ||
const fieldInfo = (0, cross_1.resolveField)(this.modelMeta, model, field); | ||
@@ -780,2 +816,6 @@ if (!fieldInfo || !fieldInfo.isDataModel) { | ||
doInjectReadCheckSelect(model, args, input) { | ||
// omit should be ignored to avoid interfering with field selection | ||
if (args.omit) { | ||
delete args.omit; | ||
} | ||
if (!(input === null || input === void 0 ? void 0 : input.select)) { | ||
@@ -924,3 +964,3 @@ return; | ||
doPostProcessForRead(data, model, fullData, queryArgs, hasFieldLevelPolicy, path = '') { | ||
var _a, _b; | ||
var _a, _b, _c; | ||
if (data === null || data === undefined) { | ||
@@ -942,2 +982,7 @@ return; | ||
} | ||
if (((_a = queryArgs === null || queryArgs === void 0 ? void 0 : queryArgs.omit) === null || _a === void 0 ? void 0 : _a[field]) === true) { | ||
// respect `{ omit: { [field]: true } }` | ||
delete entityData[field]; | ||
continue; | ||
} | ||
if (hasFieldLevelPolicy) { | ||
@@ -976,3 +1021,3 @@ // 1. remove fields selected for checking field-level policies but not selected by the original query args | ||
// recurse into nested fields | ||
const nextArgs = (_b = ((_a = queryArgs === null || queryArgs === void 0 ? void 0 : queryArgs.select) !== null && _a !== void 0 ? _a : queryArgs === null || queryArgs === void 0 ? void 0 : queryArgs.include)) === null || _b === void 0 ? void 0 : _b[field]; | ||
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]; | ||
this.doPostProcessForRead(fieldData, fieldInfo.type, entityFullData[field], nextArgs, this.hasFieldLevelPolicy(fieldInfo.type), path ? path + '.' + field : field); | ||
@@ -979,0 +1024,0 @@ } |
import { z } from 'zod'; | ||
import { FIELD_LEVEL_OVERRIDE_READ_GUARD_PREFIX, FIELD_LEVEL_OVERRIDE_UPDATE_GUARD_PREFIX, FIELD_LEVEL_READ_CHECKER_PREFIX, FIELD_LEVEL_READ_CHECKER_SELECTOR, FIELD_LEVEL_UPDATE_GUARD_PREFIX, HAS_FIELD_LEVEL_POLICY_FLAG, PRE_UPDATE_VALUE_SELECTOR } from '../constants'; | ||
import type { CrudContract, PolicyOperationKind, QueryContext } from '../types'; | ||
import type { CheckerContext, CrudContract, PolicyCrudKind, PolicyOperationKind, QueryContext } from '../types'; | ||
/** | ||
@@ -24,2 +24,49 @@ * Common options for PrismaClient enhancements | ||
/** | ||
* Function for checking if an operation is possibly allowed. | ||
*/ | ||
export type CheckerFunc = (context: CheckerContext) => CheckerConstraint; | ||
/** | ||
* Supported checker constraint checking value types. | ||
*/ | ||
export type ConstraintValueTypes = 'boolean' | 'number' | 'string'; | ||
/** | ||
* Free variable constraint | ||
*/ | ||
export type VariableConstraint = { | ||
kind: 'variable'; | ||
name: string; | ||
type: ConstraintValueTypes; | ||
}; | ||
/** | ||
* Constant value constraint | ||
*/ | ||
export type ValueConstraint = { | ||
kind: 'value'; | ||
value: number | boolean | string; | ||
type: ConstraintValueTypes; | ||
}; | ||
/** | ||
* Terms for comparison constraints | ||
*/ | ||
export type ComparisonTerm = VariableConstraint | ValueConstraint; | ||
/** | ||
* Comparison constraint | ||
*/ | ||
export type ComparisonConstraint = { | ||
kind: 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte'; | ||
left: ComparisonTerm; | ||
right: ComparisonTerm; | ||
}; | ||
/** | ||
* Logical constraint | ||
*/ | ||
export type LogicalConstraint = { | ||
kind: 'and' | 'or' | 'not'; | ||
children: CheckerConstraint[]; | ||
}; | ||
/** | ||
* Operation allowability checking constraint | ||
*/ | ||
export type CheckerConstraint = ValueConstraint | VariableConstraint | ComparisonConstraint | LogicalConstraint; | ||
/** | ||
* Function for getting policy guard with a given context | ||
@@ -41,2 +88,3 @@ */ | ||
}>; | ||
checker?: Record<string, Record<PolicyCrudKind, CheckerFunc | boolean>>; | ||
validation: Record<string, { | ||
@@ -43,0 +91,0 @@ hasValidation: boolean; |
{ | ||
"name": "@zenstackhq/runtime", | ||
"displayName": "ZenStack Runtime Library", | ||
"version": "2.0.3", | ||
"version": "2.1.0", | ||
"description": "Runtime of ZenStack for both client-side and server-side environments.", | ||
@@ -25,2 +25,6 @@ "repository": { | ||
}, | ||
"./constraint-solver": { | ||
"types": "./constraint-solver.d.ts", | ||
"default": "./constraint-solver.js" | ||
}, | ||
"./zod": { | ||
@@ -76,2 +80,3 @@ "types": "./zod/index.d.ts", | ||
"deepmerge": "^4.3.1", | ||
"logic-solver": "^2.0.1", | ||
"lower-case-first": "^2.0.2", | ||
@@ -83,2 +88,3 @@ "pluralize": "^8.0.0", | ||
"tiny-invariant": "^1.3.1", | ||
"ts-pattern": "^4.3.0", | ||
"tslib": "^2.4.1", | ||
@@ -85,0 +91,0 @@ "upper-case-first": "^2.0.2", |
@@ -28,2 +28,3 @@ export type PrismaPromise<T> = Promise<T> & Record<string, (args?: any) => PrismaPromise<any>>; | ||
subscribe(args?: unknown): Promise<any>; | ||
check(args: unknown): Promise<boolean>; | ||
fields: Record<string, any>; | ||
@@ -35,6 +36,7 @@ } | ||
export type PolicyKind = 'allow' | 'deny'; | ||
export type PolicyCrudKind = 'read' | 'create' | 'update' | 'delete'; | ||
/** | ||
* Kinds of operations controlled by access policies | ||
*/ | ||
export type PolicyOperationKind = 'create' | 'update' | 'postUpdate' | 'read' | 'delete'; | ||
export type PolicyOperationKind = PolicyCrudKind | 'postUpdate'; | ||
/** | ||
@@ -58,2 +60,15 @@ * Current login user info | ||
/** | ||
* Context for checking operation allowability. | ||
*/ | ||
export type CheckerContext = { | ||
/** | ||
* Current user | ||
*/ | ||
user?: AuthUser; | ||
/** | ||
* Extra field value filters. | ||
*/ | ||
fieldValues?: Record<string, string | number | boolean>; | ||
}; | ||
/** | ||
* Prisma contract for CRUD operations. | ||
@@ -60,0 +75,0 @@ */ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
617935
100
7741
20
+ Addedlogic-solver@^2.0.1
+ Addedts-pattern@^4.3.0
+ Addedlogic-solver@2.0.1(transitive)
+ Addedsemver@7.7.0(transitive)
+ Addedts-pattern@4.3.0(transitive)
+ Addedunderscore@1.13.7(transitive)
- Removedsemver@7.7.1(transitive)