New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@zenstackhq/runtime

Package Overview
Dependencies
Maintainers
2
Versions
276
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@zenstackhq/runtime - npm Package Compare versions

Comparing version 2.1.2 to 2.2.0

28

constants.d.ts

@@ -56,32 +56,4 @@ /**

/**
* Selector function name for fetching pre-update entity values.
*/
export declare const PRE_UPDATE_VALUE_SELECTOR = "preValueSelect";
/**
* Prefix for field-level read checker function name
*/
export declare const FIELD_LEVEL_READ_CHECKER_PREFIX = "readFieldCheck$";
/**
* Field-level access control evaluation selector function name
*/
export declare const FIELD_LEVEL_READ_CHECKER_SELECTOR = "readFieldSelect";
/**
* Prefix for field-level override read guard function name
*/
export declare const FIELD_LEVEL_OVERRIDE_READ_GUARD_PREFIX = "readFieldGuardOverride$";
/**
* Prefix for field-level update guard function name
*/
export declare const FIELD_LEVEL_UPDATE_GUARD_PREFIX = "updateFieldGuard$";
/**
* Prefix for field-level override update guard function name
*/
export declare const FIELD_LEVEL_OVERRIDE_UPDATE_GUARD_PREFIX = "updateFieldGuardOverride$";
/**
* Flag that indicates if the model has field-level access control
*/
export declare const HAS_FIELD_LEVEL_POLICY_FLAG = "hasFieldLevelPolicy";
/**
* Prefix for auxiliary relation field generated for delegated models
*/
export declare const DELEGATE_AUX_RELATION_PREFIX = "delegate_aux";

30

constants.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DELEGATE_AUX_RELATION_PREFIX = exports.HAS_FIELD_LEVEL_POLICY_FLAG = exports.FIELD_LEVEL_OVERRIDE_UPDATE_GUARD_PREFIX = exports.FIELD_LEVEL_UPDATE_GUARD_PREFIX = exports.FIELD_LEVEL_OVERRIDE_READ_GUARD_PREFIX = exports.FIELD_LEVEL_READ_CHECKER_SELECTOR = exports.FIELD_LEVEL_READ_CHECKER_PREFIX = exports.PRE_UPDATE_VALUE_SELECTOR = exports.PRISMA_MINIMUM_VERSION = exports.PRISMA_PROXY_ENHANCER = exports.PrismaErrorCode = exports.CrudFailureReason = exports.DEFAULT_PASSWORD_SALT_LENGTH = exports.DEFAULT_RUNTIME_LOAD_PATH = void 0;
exports.DELEGATE_AUX_RELATION_PREFIX = exports.PRISMA_MINIMUM_VERSION = exports.PRISMA_PROXY_ENHANCER = exports.PrismaErrorCode = exports.CrudFailureReason = exports.DEFAULT_PASSWORD_SALT_LENGTH = exports.DEFAULT_RUNTIME_LOAD_PATH = void 0;
/**

@@ -61,30 +61,2 @@ * Default path for loading CLI-generated code

/**
* Selector function name for fetching pre-update entity values.
*/
exports.PRE_UPDATE_VALUE_SELECTOR = 'preValueSelect';
/**
* Prefix for field-level read checker function name
*/
exports.FIELD_LEVEL_READ_CHECKER_PREFIX = 'readFieldCheck$';
/**
* Field-level access control evaluation selector function name
*/
exports.FIELD_LEVEL_READ_CHECKER_SELECTOR = 'readFieldSelect';
/**
* Prefix for field-level override read guard function name
*/
exports.FIELD_LEVEL_OVERRIDE_READ_GUARD_PREFIX = 'readFieldGuardOverride$';
/**
* Prefix for field-level update guard function name
*/
exports.FIELD_LEVEL_UPDATE_GUARD_PREFIX = 'updateFieldGuard$';
/**
* Prefix for field-level override update guard function name
*/
exports.FIELD_LEVEL_OVERRIDE_UPDATE_GUARD_PREFIX = 'updateFieldGuardOverride$';
/**
* Flag that indicates if the model has field-level access control
*/
exports.HAS_FIELD_LEVEL_POLICY_FLAG = 'hasFieldLevelPolicy';
/**
* Prefix for auxiliary relation field generated for delegated models

@@ -91,0 +63,0 @@ */

8

enhancements/delegate.js

@@ -19,2 +19,3 @@ "use strict";

const deepmerge_1 = __importDefault(require("deepmerge"));
const is_plain_object_1 = require("is-plain-object");
const lower_case_first_1 = require("lower-case-first");

@@ -879,2 +880,3 @@ const constants_1 = require("../constants");

arrayMerge: combineMerge,
isMergeableObject: (v) => (0, is_plain_object_1.isPlainObject)(v) || Array.isArray(v), // avoid messing with Decimal, Date, etc.
});

@@ -884,2 +886,5 @@ return result;

assembleUp(model, entity) {
if (!entity) {
return entity;
}
const result = {};

@@ -926,2 +931,5 @@ const base = this.getBaseModel(model);

assembleDown(model, entity) {
if (!entity) {
return entity;
}
const result = {};

@@ -928,0 +936,0 @@ const modelInfo = (0, cross_1.getModelInfo)(this.options.modelMeta, model, true);

@@ -1,2 +0,2 @@

import type { CheckerConstraint } from '../types';
import type { PermissionCheckerConstraint } from '../types';
/**

@@ -11,3 +11,3 @@ * A boolean constraint solver based on `logic-solver`. Only boolean and integer types are supported.

*/
checkSat(constraint: CheckerConstraint): boolean;
checkSat(constraint: PermissionCheckerConstraint): boolean;
private buildFormula;

@@ -14,0 +14,0 @@ private buildLogicalFormula;

@@ -40,2 +40,9 @@ import { PolicyCrudKind, type DbClientContract } from '../../types';

}>;
createManyAndReturn(args: {
select: any;
include: any;
data: any;
skipDuplicates?: boolean;
}): Promise<unknown[]>;
private validateCreateInput;
private doCreateMany;

@@ -72,3 +79,4 @@ private hasDuplicatedUniqueConstraint;

private removeFromParent;
private buildIdFilterWithEntityChecker;
}
export {};

@@ -6,3 +6,3 @@ import { ZodError } from 'zod';

import { QueryUtils } from '../query-utils';
import type { CheckerFunc } from '../types';
import type { EntityChecker, PermissionCheckerFunc } from '../types';
/**

@@ -38,4 +38,5 @@ * Access policy enforcement utilities

private reduce;
private readonly FULLY_OPEN_AUTH_GUARD;
private getModelAuthGuard;
private readonly FULL_OPEN_MODEL_POLICY;
private getModelPolicyDef;
private getModelGuardForOperation;
/**

@@ -49,2 +50,6 @@ * Gets pregenerated authorization guard object for a given model and operation.

/**
* Get field-level read auth guard
*/
getFieldReadAuthGuard(db: CrudContract, model: string, field: string): object;
/**
* Get field-level read auth guard that overrides the model-level

@@ -79,5 +84,5 @@ */

injectAuthGuardAsWhere(db: CrudContract, args: any, model: string, operation: PolicyOperationKind): boolean;
private injectGuardForRelationFields;
private injectGuardForToManyField;
private injectGuardForToOneField;
private injectReadGuardForRelationFields;
private injectReadGuardForToManyField;
private injectReadGuardForToOneField;
/**

@@ -90,4 +95,3 @@ * Injects auth guard for read operations.

*/
getCheckerConstraint(model: string, operation: PolicyCrudKind): ReturnType<CheckerFunc> | boolean;
private getModelChecker;
getCheckerConstraint(model: string, operation: PolicyCrudKind): ReturnType<PermissionCheckerFunc> | boolean;
/**

@@ -103,4 +107,7 @@ * Gets unique constraints for the given model.

checkPolicyForUnique(model: string, uniqueFilter: any, operation: PolicyOperationKind, db: CrudContract, args: any, preValue?: any): Promise<void>;
getEntityChecker(model: string, operation: PolicyOperationKind, field?: string): EntityChecker | undefined;
getUpdateOverrideEntityCheckerForField(model: string, field: string): EntityChecker | undefined;
private getFieldReadGuards;
private getFieldUpdateGuards;
private combineEntityChecker;
/**

@@ -125,4 +132,4 @@ * Tries rejecting a request based on static "false" policy.

/**
* Injects field selection needed for checking field-level read policy into query args.
* @returns
* Injects field selection needed for checking field-level read policy check and evaluating
* entity checker into query args.
*/

@@ -138,3 +145,3 @@ injectReadCheckSelect(model: string, args: any): void;

getPreValueSelect(model: string): object | undefined;
private getReadFieldSelect;
private getFieldReadCheckSelector;
private checkReadField;

@@ -152,3 +159,3 @@ private hasFieldValidation;

*/
postProcessForRead(data: any, model: string, queryArgs: any): void;
postProcessForRead(data: any, model: string, queryArgs: any): any;
private doPostProcessForRead;

@@ -168,3 +175,2 @@ /**

private mergeWhereClause;
private requireGuard;
/**

@@ -171,0 +177,0 @@ * Given an entity data, returns an object only containing id fields.

@@ -18,2 +18,3 @@ "use strict";

const deepcopy_1 = __importDefault(require("deepcopy"));
const deepmerge_1 = __importDefault(require("deepmerge"));
const lower_case_first_1 = require("lower-case-first");

@@ -38,10 +39,10 @@ const upper_case_first_1 = require("upper-case-first");

//#region Auth guard
this.FULLY_OPEN_AUTH_GUARD = {
create: true,
read: true,
update: true,
delete: true,
postUpdate: true,
create_input: true,
update_input: true,
this.FULL_OPEN_MODEL_POLICY = {
modelLevel: {
read: { guard: true },
create: { guard: true, inputChecker: true },
update: { guard: true },
delete: { guard: true },
postUpdate: { guard: true },
},
};

@@ -220,11 +221,18 @@ this.logger = new logger_1.Logger(db);

}
getModelAuthGuard(model) {
getModelPolicyDef(model) {
if (this.options.kinds && !this.options.kinds.includes('policy')) {
// policy enhancement not enabled, return an fully open guard
return this.FULLY_OPEN_AUTH_GUARD;
return this.FULL_OPEN_MODEL_POLICY;
}
else {
return this.policy.guard[(0, lower_case_first_1.lowerCaseFirst)(model)];
const def = this.policy.policy[(0, lower_case_first_1.lowerCaseFirst)(model)];
if (!def) {
throw this.unknownError(`unable to load policy guard for ${model}`);
}
return def;
}
getModelGuardForOperation(model, operation) {
var _a;
const def = this.getModelPolicyDef(model);
return (_a = def.modelLevel[operation].guard) !== null && _a !== void 0 ? _a : true;
}
/**

@@ -237,14 +245,26 @@ * Gets pregenerated authorization guard object for a given model and operation.

getAuthGuard(db, model, operation, preValue) {
const guard = this.getModelAuthGuard(model);
if (!guard) {
throw this.unknownError(`unable to load policy guard for ${model}`);
const guard = this.getModelGuardForOperation(model, operation);
// constant guard
if (typeof guard === 'boolean') {
return this.reduce(guard);
}
const provider = guard[operation];
if (typeof provider === 'boolean') {
return this.reduce(provider);
// invoke guard function
const r = guard({ user: this.user, preValue }, db);
return this.reduce(r);
}
/**
* Get field-level read auth guard
*/
getFieldReadAuthGuard(db, model, field) {
var _a, _b, _c;
const def = this.getModelPolicyDef(model);
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;
if (guard === undefined) {
// field access is allowed by default
return this.makeTrue();
}
if (!provider) {
throw this.unknownError(`unable to load authorization guard for ${model}`);
if (typeof guard === 'boolean') {
return this.reduce(guard);
}
const r = provider({ user: this.user, preValue }, db);
const r = guard({ user: this.user }, db);
return this.reduce(r);

@@ -256,12 +276,13 @@ }

getFieldOverrideReadAuthGuard(db, model, field) {
const guard = this.requireGuard(model);
const provider = guard[`${constants_1.FIELD_LEVEL_OVERRIDE_READ_GUARD_PREFIX}${field}`];
if (provider === undefined) {
var _a, _b, _c;
const def = this.getModelPolicyDef(model);
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;
if (guard === undefined) {
// field access is denied by default in override mode
return this.makeFalse();
}
if (typeof provider === 'boolean') {
return this.reduce(provider);
if (typeof guard === 'boolean') {
return this.reduce(guard);
}
const r = provider({ user: this.user }, db);
const r = guard({ user: this.user }, db);
return this.reduce(r);

@@ -273,12 +294,13 @@ }

getFieldUpdateAuthGuard(db, model, field) {
const guard = this.requireGuard(model);
const provider = guard[`${constants_1.FIELD_LEVEL_UPDATE_GUARD_PREFIX}${field}`];
if (provider === undefined) {
var _a, _b, _c;
const def = this.getModelPolicyDef(model);
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;
if (guard === undefined) {
// field access is allowed by default
return this.makeTrue();
}
if (typeof provider === 'boolean') {
return this.reduce(provider);
if (typeof guard === 'boolean') {
return this.reduce(guard);
}
const r = provider({ user: this.user }, db);
const r = guard({ user: this.user }, db);
return this.reduce(r);

@@ -290,12 +312,13 @@ }

getFieldOverrideUpdateAuthGuard(db, model, field) {
const guard = this.requireGuard(model);
const provider = guard[`${constants_1.FIELD_LEVEL_OVERRIDE_UPDATE_GUARD_PREFIX}${field}`];
if (provider === undefined) {
var _a, _b, _c;
const def = this.getModelPolicyDef(model);
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;
if (guard === undefined) {
// field access is denied by default in override mode
return this.makeFalse();
}
if (typeof provider === 'boolean') {
return this.reduce(provider);
if (typeof guard === 'boolean') {
return this.reduce(guard);
}
const r = provider({ user: this.user }, db);
const r = guard({ user: this.user }, db);
return this.reduce(r);

@@ -307,8 +330,4 @@ }

hasAuthGuard(model, operation) {
const guard = this.getModelAuthGuard(model);
if (!guard) {
return false;
}
const provider = guard[operation];
return typeof provider !== 'boolean' || provider !== true;
const guard = this.getModelGuardForOperation(model, operation);
return typeof guard !== 'boolean' || guard !== true;
}

@@ -319,11 +338,13 @@ /**

hasOverrideAuthGuard(model, operation) {
const guard = this.requireGuard(model);
switch (operation) {
case 'read':
return Object.keys(guard).some((k) => k.startsWith(constants_1.FIELD_LEVEL_OVERRIDE_READ_GUARD_PREFIX));
case 'update':
return Object.keys(guard).some((k) => k.startsWith(constants_1.FIELD_LEVEL_OVERRIDE_UPDATE_GUARD_PREFIX));
default:
return false;
var _a;
if (operation !== 'read' && operation !== 'update') {
return false;
}
const def = this.getModelPolicyDef(model);
if ((_a = def.fieldLevel) === null || _a === void 0 ? void 0 : _a[operation]) {
return Object.values(def.fieldLevel[operation]).some((f) => f.overrideGuard !== undefined || f.overrideEntityChecker !== undefined);
}
else {
return false;
}
}

@@ -336,14 +357,11 @@ /**

checkInputGuard(model, args, operation) {
const guard = this.getModelAuthGuard(model);
if (!guard) {
const def = this.getModelPolicyDef(model);
const guard = def.modelLevel[operation].inputChecker;
if (guard === undefined) {
return undefined;
}
const provider = guard[`${operation}_input`];
if (typeof provider === 'boolean') {
return provider;
if (typeof guard === 'boolean') {
return guard;
}
if (!provider) {
return undefined;
}
return provider(args, { user: this.user });
return guard(args, { user: this.user });
}

@@ -385,2 +403,3 @@ /**

}
let mergedGuard = guard;
if (args.where) {

@@ -390,8 +409,15 @@ // inject into relation fields:

// to-one: direct-conditions/is/isNot
this.injectGuardForRelationFields(db, model, args.where, operation);
mergedGuard = this.injectReadGuardForRelationFields(db, model, args.where, guard);
}
args.where = this.and(args.where, guard);
args.where = this.and(args.where, mergedGuard);
return true;
}
injectGuardForRelationFields(db, model, payload, operation) {
// Injects guard for relation fields nested in `payload`. The `modelGuard` parameter represents the model-level guard for `model`.
// The function returns a modified copy of `modelGuard` with field-level policies combined.
injectReadGuardForRelationFields(db, model, payload, modelGuard) {
if (!payload || typeof payload !== 'object' || Object.keys(payload).length === 0) {
return modelGuard;
}
const allFieldGuards = [];
const allFieldOverrideGuards = [];
for (const [field, subPayload] of Object.entries(payload)) {

@@ -401,25 +427,33 @@ if (!subPayload) {

}
allFieldGuards.push(this.getFieldReadAuthGuard(db, model, field));
allFieldOverrideGuards.push(this.getFieldOverrideReadAuthGuard(db, model, field));
const fieldInfo = (0, cross_1.resolveField)(this.modelMeta, model, field);
if (!fieldInfo || !fieldInfo.isDataModel) {
continue;
if (fieldInfo === null || fieldInfo === void 0 ? void 0 : fieldInfo.isDataModel) {
if (fieldInfo.isArray) {
this.injectReadGuardForToManyField(db, fieldInfo, subPayload);
}
else {
this.injectReadGuardForToOneField(db, fieldInfo, subPayload);
}
}
if (fieldInfo.isArray) {
this.injectGuardForToManyField(db, fieldInfo, subPayload, operation);
}
else {
this.injectGuardForToOneField(db, fieldInfo, subPayload, operation);
}
}
// all existing field-level guards must be true
const mergedGuard = this.and(...allFieldGuards);
// all existing field-level override guards must be true for override to take effect; override is disabled by default
const mergedOverrideGuard = allFieldOverrideGuards.length === 0 ? this.makeFalse() : this.and(...allFieldOverrideGuards);
// (original-guard && field-level-guard) || field-level-override-guard
const updatedGuard = this.or(this.and(modelGuard, mergedGuard), mergedOverrideGuard);
return updatedGuard;
}
injectGuardForToManyField(db, fieldInfo, payload, operation) {
const guard = this.getAuthGuard(db, fieldInfo.type, operation);
injectReadGuardForToManyField(db, fieldInfo, payload) {
const guard = this.getAuthGuard(db, fieldInfo.type, 'read');
if (payload.some) {
this.injectGuardForRelationFields(db, fieldInfo.type, payload.some, operation);
const mergedGuard = this.injectReadGuardForRelationFields(db, fieldInfo.type, payload.some, guard);
// turn "some" into: { some: { AND: [guard, payload.some] } }
payload.some = this.and(payload.some, guard);
payload.some = this.and(payload.some, mergedGuard);
}
if (payload.none) {
this.injectGuardForRelationFields(db, fieldInfo.type, payload.none, operation);
const mergedGuard = this.injectReadGuardForRelationFields(db, fieldInfo.type, payload.none, guard);
// turn none into: { none: { AND: [guard, payload.none] } }
payload.none = this.and(payload.none, guard);
payload.none = this.and(payload.none, mergedGuard);
}

@@ -430,3 +464,3 @@ if (payload.every &&

Object.keys(payload.every).length > 0) {
this.injectGuardForRelationFields(db, fieldInfo.type, payload.every, operation);
const mergedGuard = this.injectReadGuardForRelationFields(db, fieldInfo.type, payload.every, guard);
// turn "every" into: { none: { AND: [guard, { NOT: payload.every }] } }

@@ -436,23 +470,26 @@ if (!payload.none) {

}
payload.none = this.and(payload.none, guard, this.not(payload.every));
payload.none = this.and(payload.none, mergedGuard, this.not(payload.every));
delete payload.every;
}
}
injectGuardForToOneField(db, fieldInfo, payload, operation) {
const guard = this.getAuthGuard(db, fieldInfo.type, operation);
injectReadGuardForToOneField(db, fieldInfo, payload) {
const guard = this.getAuthGuard(db, fieldInfo.type, 'read');
// is|isNot and flat fields conditions are mutually exclusive
if (payload.is || payload.isNot) {
// is and isNot can be null value
if (payload.is !== undefined || payload.isNot !== undefined) {
if (payload.is) {
this.injectGuardForRelationFields(db, fieldInfo.type, payload.is, operation);
const mergedGuard = this.injectReadGuardForRelationFields(db, fieldInfo.type, payload.is, guard);
// merge guard with existing "is": { is: { AND: [originalIs, guard] } }
payload.is = this.and(payload.is, mergedGuard);
}
if (payload.isNot) {
this.injectGuardForRelationFields(db, fieldInfo.type, payload.isNot, operation);
const mergedGuard = this.injectReadGuardForRelationFields(db, fieldInfo.type, payload.isNot, guard);
// merge guard with existing "isNot": { isNot: { AND: [originalIsNot, guard] } }
payload.isNot = this.and(payload.isNot, mergedGuard);
}
// merge guard with existing "is": { is: [originalIs, guard] }
payload.is = this.and(payload.is, guard);
}
else {
this.injectGuardForRelationFields(db, fieldInfo.type, payload, operation);
const mergedGuard = this.injectReadGuardForRelationFields(db, fieldInfo.type, payload, guard);
// turn direct conditions into: { is: { AND: [ originalConditions, guard ] } }
const combined = this.and((0, deepcopy_1.default)(payload), guard);
const combined = this.and((0, deepcopy_1.default)(payload), mergedGuard);
Object.keys(payload).forEach((key) => delete payload[key]);

@@ -475,3 +512,3 @@ payload.is = combined;

// to-one: direct-conditions/is/isNot
this.injectGuardForRelationFields(db, model, args.where, 'read');
this.injectReadGuardForRelationFields(db, model, args.where, {});
}

@@ -506,28 +543,20 @@ if (injected.where && Object.keys(injected.where).length > 0 && !this.isTrue(injected.where)) {

getCheckerConstraint(model, operation) {
const checker = this.getModelChecker(model);
const provider = checker[operation];
if (typeof provider === 'boolean') {
return provider;
if (this.options.kinds && !this.options.kinds.includes('policy')) {
// policy enhancement not enabled, return a constant true checker result
return true;
}
if (typeof provider !== 'function') {
const def = this.getModelPolicyDef(model);
const checker = def.modelLevel[operation].permissionChecker;
if (checker === undefined) {
throw new Error(`Generated permission checkers not found. Please make sure the "generatePermissionChecker" option is set to true in the "@core/enhancer" plugin.`);
}
if (typeof checker === 'boolean') {
return checker;
}
if (typeof checker !== 'function') {
throw this.unknownError(`invalid ${operation} checker function for ${model}`);
}
// call checker function
return provider({ user: this.user });
return checker({ 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

@@ -629,2 +658,3 @@ /**

}
let entityChecker;
if (operation === 'update' && args) {

@@ -637,20 +667,23 @@ // merge field-level policy guards

}
else {
if (fieldUpdateGuard.guard) {
// merge field-level guard
guard = this.and(guard, fieldUpdateGuard.guard);
}
if (fieldUpdateGuard.overrideGuard) {
// merge field-level override guard
guard = this.or(guard, fieldUpdateGuard.overrideGuard);
}
if (fieldUpdateGuard.guard) {
// merge field-level guard with AND
guard = this.and(guard, fieldUpdateGuard.guard);
}
if (fieldUpdateGuard.overrideGuard) {
// merge field-level override guard with OR
guard = this.or(guard, fieldUpdateGuard.overrideGuard);
}
// field-level entity checker
entityChecker = fieldUpdateGuard.entityChecker;
}
// Zod schema is to be checked for "create" and "postUpdate"
const schema = ['create', 'postUpdate'].includes(operation) ? this.getZodSchema(model) : undefined;
if (this.isTrue(guard) && !schema) {
// combine field-level entity checker with model-level
const modelEntityChecker = this.getEntityChecker(model, operation);
entityChecker = this.combineEntityChecker(entityChecker, modelEntityChecker, 'and');
if (this.isTrue(guard) && !schema && !entityChecker) {
// unconditionally allowed
return;
}
const select = schema
let select = schema
? // need to validate against schema, need to fetch all fields

@@ -660,2 +693,8 @@ undefined

this.makeIdSelection(model);
if (entityChecker === null || entityChecker === void 0 ? void 0 : entityChecker.selector) {
if (!select) {
select = this.makeAllScalarFieldSelect(model);
}
select = Object.assign(Object.assign({}, select), entityChecker.selector);
}
let where = this.clone(uniqueFilter);

@@ -674,2 +713,10 @@ // query args may have be of combined-id form, need to flatten it to call findFirst

}
if (entityChecker) {
if (this.logger.enabled('info')) {
this.logger.info(`[policy] running entity checker on ${model} for ${operation}`);
}
if (!entityChecker.func(result, { user: this.user, preValue })) {
throw this.deniedByPolicy(model, operation, `entity ${(0, utils_1.formatObject)(uniqueFilter, false)} failed policy check`, constants_1.CrudFailureReason.ACCESS_POLICY_VIOLATION);
}
}
if (schema) {

@@ -688,2 +735,17 @@ // TODO: push down schema check to the database

}
getEntityChecker(model, operation, field) {
var _a, _b, _c;
const def = this.getModelPolicyDef(model);
if (field) {
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;
}
else {
return def.modelLevel[operation].entityChecker;
}
}
getUpdateOverrideEntityCheckerForField(model, field) {
var _a, _b, _c;
const def = this.getModelPolicyDef(model);
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;
}
getFieldReadGuards(db, model, args) {

@@ -712,12 +774,13 @@ const allFields = Object.values((0, cross_1.getFields)(this.modelMeta, model));

const allOverrideFieldGuards = [];
for (const [k, v] of Object.entries((_a = args.data) !== null && _a !== void 0 ? _a : args)) {
if (typeof v === 'undefined') {
let entityChecker;
for (const [field, value] of Object.entries((_a = args.data) !== null && _a !== void 0 ? _a : args)) {
if (typeof value === 'undefined') {
continue;
}
const field = (0, cross_1.resolveField)(this.modelMeta, model, k);
if (field === null || field === void 0 ? void 0 : field.isDataModel) {
const fieldInfo = (0, cross_1.resolveField)(this.modelMeta, model, field);
if (fieldInfo === null || fieldInfo === void 0 ? void 0 : fieldInfo.isDataModel) {
// relation field update should be treated as foreign key update,
// fetch and merge all foreign key guards
if (field.isRelationOwner && field.foreignKeyMapping) {
const foreignKeys = Object.values(field.foreignKeyMapping);
if (fieldInfo.isRelationOwner && fieldInfo.foreignKeyMapping) {
const foreignKeys = Object.values(fieldInfo.foreignKeyMapping);
for (const fk of foreignKeys) {

@@ -737,5 +800,5 @@ const fieldGuard = this.getFieldUpdateAuthGuard(db, model, fk);

else {
const fieldGuard = this.getFieldUpdateAuthGuard(db, model, k);
const fieldGuard = this.getFieldUpdateAuthGuard(db, model, field);
if (this.isFalse(fieldGuard)) {
return { guard: fieldGuard, rejectedByField: k };
return { guard: fieldGuard, rejectedByField: field };
}

@@ -745,5 +808,11 @@ // add field guard

// add field override guard
const overrideFieldGuard = this.getFieldOverrideUpdateAuthGuard(db, model, k);
const overrideFieldGuard = this.getFieldOverrideUpdateAuthGuard(db, model, field);
allOverrideFieldGuards.push(overrideFieldGuard);
}
// merge regular and override entity checkers with OR
let checker = this.getEntityChecker(model, 'update', field);
const overrideChecker = this.getUpdateOverrideEntityCheckerForField(model, field);
checker = this.combineEntityChecker(checker, overrideChecker, 'or');
// accumulate entity checker across fields
entityChecker = this.combineEntityChecker(entityChecker, checker, 'and');
}

@@ -756,4 +825,21 @@ const allFieldsCombined = this.and(...allFieldGuards);

rejectedByField: undefined,
entityChecker,
};
}
combineEntityChecker(left, right, combiner) {
var _a, _b;
if (!left) {
return right;
}
if (!right) {
return left;
}
const func = combiner === 'and'
? (entity, context) => left.func(entity, context) && right.func(entity, context)
: (entity, context) => left.func(entity, context) || right.func(entity, context);
return {
func,
selector: (0, deepmerge_1.default)((_a = left.selector) !== null && _a !== void 0 ? _a : {}, (_b = right.selector) !== null && _b !== void 0 ? _b : {}),
};
}
/**

@@ -817,4 +903,4 @@ * Tries rejecting a request based on static "false" policy.

/**
* Injects field selection needed for checking field-level read policy into query args.
* @returns
* Injects field selection needed for checking field-level read policy check and evaluating
* entity checker into query args.
*/

@@ -835,3 +921,3 @@ injectReadCheckSelect(model, args) {

// recursively inject selection for fields needed for field-level read checks
const readFieldSelect = this.getReadFieldSelect(model);
const readFieldSelect = this.getFieldReadCheckSelector(model);
if (readFieldSelect) {

@@ -841,2 +927,6 @@ this.doInjectReadCheckSelect(model, args, { select: readFieldSelect });

}
const entityChecker = this.getEntityChecker(model, 'read');
if (entityChecker === null || entityChecker === void 0 ? void 0 : entityChecker.selector) {
this.doInjectReadCheckSelect(model, args, { select: entityChecker.selector });
}
}

@@ -931,26 +1021,35 @@ doInjectReadCheckSelect(model, args, input) {

getPreValueSelect(model) {
const guard = this.getModelAuthGuard(model);
if (!guard) {
throw this.unknownError(`unable to load policy guard for ${model}`);
}
return guard[constants_1.PRE_UPDATE_VALUE_SELECTOR];
const def = this.getModelPolicyDef(model);
return def.modelLevel.postUpdate.preUpdateSelector;
}
getReadFieldSelect(model) {
const guard = this.getModelAuthGuard(model);
if (!guard) {
throw this.unknownError(`unable to load policy guard for ${model}`);
// get a merged selector object for all field-level read policies
getFieldReadCheckSelector(model) {
var _a, _b, _c;
const def = this.getModelPolicyDef(model);
let result = {};
const fieldLevel = (_a = def.fieldLevel) === null || _a === void 0 ? void 0 : _a.read;
if (fieldLevel) {
for (const def of Object.values(fieldLevel)) {
if ((_b = def.entityChecker) === null || _b === void 0 ? void 0 : _b.selector) {
result = (0, deepmerge_1.default)(result, def.entityChecker.selector);
}
if ((_c = def.overrideEntityChecker) === null || _c === void 0 ? void 0 : _c.selector) {
result = (0, deepmerge_1.default)(result, def.overrideEntityChecker.selector);
}
}
}
return guard[constants_1.FIELD_LEVEL_READ_CHECKER_SELECTOR];
return Object.keys(result).length > 0 ? result : undefined;
}
checkReadField(model, field, entity) {
const guard = this.getModelAuthGuard(model);
if (!guard) {
throw this.unknownError(`unable to load policy guard for ${model}`);
}
const func = guard[`${constants_1.FIELD_LEVEL_READ_CHECKER_PREFIX}${field}`];
if (!func) {
var _a, _b, _c, _d, _e, _f;
const def = this.getModelPolicyDef(model);
// combine regular and override field-level entity checkers with OR
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;
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;
const combinedChecker = this.combineEntityChecker(checker, overrideChecker, 'or');
if (combinedChecker === undefined) {
return true;
}
else {
return func(entity, { user: this.user });
return combinedChecker.func(entity, { user: this.user });
}

@@ -963,7 +1062,5 @@ }

hasFieldLevelPolicy(model) {
const guard = this.getModelAuthGuard(model);
if (!guard) {
throw this.unknownError(`unable to load policy guard for ${model}`);
}
return !!guard[constants_1.HAS_FIELD_LEVEL_POLICY_FLAG];
var _a, _b;
const def = this.getModelPolicyDef(model);
return Object.keys((_b = (_a = def.fieldLevel) === null || _a === void 0 ? void 0 : _a.read) !== null && _b !== void 0 ? _b : {}).length > 0;
}

@@ -990,3 +1087,3 @@ /**

const origData = this.clone(data);
this.doPostProcessForRead(data, model, origData, queryArgs, this.hasFieldLevelPolicy(model));
return this.doPostProcessForRead(data, model, origData, queryArgs, this.hasFieldLevelPolicy(model));
}

@@ -996,7 +1093,35 @@ doPostProcessForRead(data, model, fullData, queryArgs, hasFieldLevelPolicy, path = '') {

if (data === null || data === undefined) {
return;
return data;
}
for (const [entityData, entityFullData] of (0, cross_1.zip)(data, fullData)) {
let filteredData = data;
let filteredFullData = fullData;
const entityChecker = this.getEntityChecker(model, 'read');
if (entityChecker) {
if (Array.isArray(data)) {
filteredData = [];
filteredFullData = [];
for (const [entityData, entityFullData] of (0, cross_1.zip)(data, fullData)) {
if (!entityChecker.func(entityData, { user: this.user })) {
if (this.shouldLogQuery) {
this.logger.info(`[policy] dropping ${model} entity${path ? ' at ' + path : ''} due to entity checker`);
}
}
else {
filteredData.push(entityData);
filteredFullData.push(entityFullData);
}
}
}
else {
if (!entityChecker.func(data, { user: this.user })) {
if (this.shouldLogQuery) {
this.logger.info(`[policy] dropping ${model} entity${path ? ' at ' + path : ''} due to entity checker`);
}
return null;
}
}
}
for (const [entityData, entityFullData] of (0, cross_1.zip)(filteredData, filteredFullData)) {
if (typeof entityData !== 'object' || !entityData) {
return;
continue;
}

@@ -1051,6 +1176,13 @@ for (const [field, fieldData] of Object.entries(entityData)) {

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);
const nestedResult = this.doPostProcessForRead(fieldData, fieldInfo.type, entityFullData[field], nextArgs, this.hasFieldLevelPolicy(fieldInfo.type), path ? path + '.' + field : field);
if (nestedResult === undefined) {
delete entityData[field];
}
else {
entityData[field] = nestedResult;
}
}
}
}
return filteredData;
}

@@ -1117,9 +1249,2 @@ /**

}
requireGuard(model) {
const guard = this.getModelAuthGuard(model);
if (!guard) {
throw this.unknownError(`unable to load policy guard for ${model}`);
}
return guard;
}
/**

@@ -1126,0 +1251,0 @@ * Given an entity data, returns an object only containing id fields.

@@ -28,2 +28,8 @@ import type { ModelMeta } from '../cross';

}): Promise<BatchResult>;
createManyAndReturn(args: {
data: any;
select: any;
include: any;
skipDuplicates?: boolean;
}): Promise<unknown[]>;
update(args: any): Promise<unknown>;

@@ -67,2 +73,8 @@ updateMany(args: any): Promise<BatchResult>;

}>;
createManyAndReturn(args: {
data: any;
select: any;
include: any;
skipDuplicates?: boolean;
}): Promise<unknown[]>;
update(args: any): Promise<unknown>;

@@ -69,0 +81,0 @@ updateMany(args: any): Promise<{

@@ -68,2 +68,5 @@ "use strict";

}
createManyAndReturn(args) {
return this.deferred('createManyAndReturn', args);
}
update(args) {

@@ -70,0 +73,0 @@ return this.deferred('update', args);

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 { CheckerContext, CrudContract, PolicyCrudKind, PolicyOperationKind, QueryContext } from '../types';
import type { CrudContract, PermissionCheckerContext, QueryContext } from '../types';
/**

@@ -24,5 +23,9 @@ * Common options for PrismaClient enhancements

/**
* Function for checking an entity's data for permission
*/
export type EntityCheckerFunc = (input: any, context: QueryContext) => boolean;
/**
* Function for checking if an operation is possibly allowed.
*/
export type CheckerFunc = (context: CheckerContext) => CheckerConstraint;
export type PermissionCheckerFunc = (context: PermissionCheckerContext) => PermissionCheckerConstraint;
/**

@@ -65,3 +68,3 @@ * Supported checker constraint checking value types.

kind: 'and' | 'or' | 'not';
children: CheckerConstraint[];
children: PermissionCheckerConstraint[];
};

@@ -71,21 +74,8 @@ /**

*/
export type CheckerConstraint = ValueConstraint | VariableConstraint | ComparisonConstraint | LogicalConstraint;
export type PermissionCheckerConstraint = ValueConstraint | VariableConstraint | ComparisonConstraint | LogicalConstraint;
/**
* Function for getting policy guard with a given context
*/
export type InputCheckFunc = (args: any, context: QueryContext) => boolean;
/**
* Function for getting policy guard with a given context
*/
export type ReadFieldCheckFunc = (input: any, context: QueryContext) => boolean;
/**
* Policy definition
*/
export type PolicyDef = {
guard: Record<string, Partial<Record<PolicyOperationKind, PolicyFunc | boolean>> & Partial<Record<`${PolicyOperationKind}_input`, InputCheckFunc | boolean>> & Record<`${typeof FIELD_LEVEL_READ_CHECKER_PREFIX}${string}`, ReadFieldCheckFunc> & Record<`${typeof FIELD_LEVEL_OVERRIDE_READ_GUARD_PREFIX}${string}` | `${typeof FIELD_LEVEL_UPDATE_GUARD_PREFIX}${string}` | `${typeof FIELD_LEVEL_OVERRIDE_UPDATE_GUARD_PREFIX}${string}`, PolicyFunc> & {
[PRE_UPDATE_VALUE_SELECTOR]?: object;
[FIELD_LEVEL_READ_CHECKER_SELECTOR]?: object;
[HAS_FIELD_LEVEL_POLICY_FLAG]?: boolean;
}>;
checker?: Record<string, Record<PolicyCrudKind, CheckerFunc | boolean>>;
policy: Record<ModelName, ModelPolicyDef>;
validation: Record<string, {

@@ -96,8 +86,150 @@ hasValidation: boolean;

};
type ModelName = string;
type FieldName = string;
/**
* Policy definition for a model
*/
export type ModelPolicyDef = {
/**
* Model-level CRUD policies
*/
modelLevel: ModelCrudDef;
/**
* Field-level CRUD policies
*/
fieldLevel?: FieldCrudDef;
};
/**
* CRUD policy definitions for a model
*/
export type ModelCrudDef = {
read: ModelReadDef;
create: ModelCreateDef;
update: ModelUpdateDef;
delete: ModelDeleteDef;
postUpdate: ModelPostUpdateDef;
};
/**
* Information for checking entity data outside of Prisma
*/
export type EntityChecker = {
/**
* Checker function
*/
func: EntityCheckerFunc;
/**
* Selector for fetching entity data
*/
selector?: object;
};
/**
* Common policy definition for a CRUD operation
*/
type ModelCrudCommon = {
/**
* Prisma query guard or a constant condition
*/
guard: PolicyFunc | boolean;
/**
* Additional checker function for checking policies outside of Prisma
*/
/**
* Additional checker function for checking policies outside of Prisma
*/
entityChecker?: EntityChecker;
/**
* Permission checker function or a constant condition
*/
permissionChecker?: PermissionCheckerFunc | boolean;
};
/**
* Policy definition for reading a model
*/
type ModelReadDef = ModelCrudCommon;
/**
* Policy definition for creating a model
*/
type ModelCreateDef = ModelCrudCommon & {
/**
* Create input validation function. Only generated when a create
* can be approved or denied based on input values.
*/
inputChecker?: EntityCheckerFunc | boolean;
};
/**
* Policy definition for updating a model
*/
type ModelUpdateDef = ModelCrudCommon;
/**
* Policy definition for deleting a model
*/
type ModelDeleteDef = ModelCrudCommon;
/**
* Policy definition for post-update checking a model
*/
type ModelPostUpdateDef = Exclude<ModelCrudCommon, 'permissionChecker'> & {
preUpdateSelector?: object;
};
/**
* CRUD policy definitions for a field
*/
type FieldCrudDef = {
/**
* Field-level read policy
*/
read: Record<FieldName, FieldReadDef>;
/**
* Field-level update policy
*/
update: Record<FieldName, FieldUpdateDef>;
};
type FieldReadDef = {
/**
* Field-level Prisma query guard
*/
guard?: PolicyFunc;
/**
* Entity checker
*/
entityChecker?: EntityChecker;
/**
* Field-level read override Prisma query guard
*/
overrideGuard?: PolicyFunc;
/**
* Entity checker for override policies
*/
overrideEntityChecker?: EntityChecker;
};
type FieldUpdateDef = {
/**
* Field-level update Prisma query guard
*/
guard?: PolicyFunc;
/**
* Additional entity checker
*/
entityChecker?: EntityChecker;
/**
* Field-level update override Prisma query guard
*/
overrideGuard?: PolicyFunc;
/**
* Additional entity checker for override policies
*/
overrideEntityChecker?: EntityChecker;
};
/**
* Zod schemas for validation
*/
export type ZodSchemas = {
/**
* Zod schema for each model
*/
models: Record<string, z.ZodSchema>;
/**
* Zod schema for Prisma input types for each model
*/
input?: Record<string, Record<string, z.ZodSchema>>;
};
export {};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const constants_1 = require("../constants");
//# sourceMappingURL=types.js.map
{
"name": "@zenstackhq/runtime",
"displayName": "ZenStack Runtime Library",
"version": "2.1.2",
"version": "2.2.0",
"description": "Runtime of ZenStack for both client-side and server-side environments.",

@@ -79,2 +79,3 @@ "repository": {

"deepmerge": "^4.3.1",
"is-plain-object": "^5.0.0",
"logic-solver": "^2.0.1",

@@ -95,3 +96,3 @@ "lower-case-first": "^2.0.2",

"peerDependencies": {
"@prisma/client": "5.0.0 - 5.13.x"
"@prisma/client": "5.0.0 - 5.15.x"
},

@@ -112,3 +113,3 @@ "author": {

"clean": "rimraf dist",
"build": "pnpm lint --max-warnings=0 && pnpm clean && tsc && tsup-node --config ./tsup-browser.config.ts && tsup-node --config ./tsup-cross.config.ts && copyfiles ./package.json ./README.md ../../LICENSE dist && copyfiles -u1 'res/**/*' dist && pnpm pack dist --pack-destination '../../../.build'",
"build": "pnpm lint --max-warnings=0 && pnpm clean && tsc && tsup-node --config ./tsup-browser.config.ts && tsup-node --config ./tsup-cross.config.ts && copyfiles ./package.json ./README.md ../../LICENSE dist && copyfiles -u1 \"res/**/*\" dist && pnpm pack dist --pack-destination ../../../.build",
"watch": "concurrently \"tsc --watch\" \"tsup-node --config ./tsup-browser.config.ts --watch\" \"tsup-node --config ./tsup-cross.config.ts --watch\"",

@@ -115,0 +116,0 @@ "lint": "eslint src --ext ts"

@@ -12,5 +12,6 @@ export type PrismaPromise<T> = Promise<T> & Record<string, (args?: any) => PrismaPromise<any>>;

create(args: unknown): Promise<any>;
createMany(args: unknown, skipDuplicates?: boolean): Promise<{
createMany(args: unknown): Promise<{
count: number;
}>;
createManyAndReturn(args: unknown): Promise<unknown[]>;
update(args: unknown): Promise<any>;

@@ -61,3 +62,3 @@ updateMany(args: unknown): Promise<{

*/
export type CheckerContext = {
export type PermissionCheckerContext = {
/**

@@ -64,0 +65,0 @@ * Current user

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc