UNPKG

prisma-rls

Version:

Prisma client extension for row-level security on any database

636 lines (635 loc) 38.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ModelResolver = void 0; const errors_1 = require("./errors"); const utils_1 = require("./utils"); class ModelResolver { constructor(permissionsConfig, context, fieldsMap, uniqueFieldsMap, authorizationError, checkRequiredBelongsTo) { this.permissionsConfig = permissionsConfig; this.context = context; this.fieldsMap = fieldsMap; this.uniqueFieldsMap = uniqueFieldsMap; this.authorizationError = authorizationError; this.checkRequiredBelongsTo = checkRequiredBelongsTo; } async resolveWhere(permissionDefinition, modelName, where) { if (!permissionDefinition) { return (0, utils_1.generateImpossibleWhere)(this.fieldsMap[modelName]); } else if (permissionDefinition === true) { return where; } else { return (0, utils_1.mergeWhere)(where, await (0, utils_1.resolvePermissionDefinition)(permissionDefinition, this.context)); } } async resolveWhereUnique(permissionDefinition, modelName, whereUnique) { if (!permissionDefinition) { return (0, utils_1.mergeWhereUnique)(this.fieldsMap[modelName], this.uniqueFieldsMap[modelName], whereUnique, (0, utils_1.generateImpossibleWhere)(this.fieldsMap[modelName])); } else if (permissionDefinition === true) { return whereUnique; } else { return (0, utils_1.mergeWhereUnique)(this.fieldsMap[modelName], this.uniqueFieldsMap[modelName], whereUnique, await (0, utils_1.resolvePermissionDefinition)(permissionDefinition, this.context)); } } async generateModelRelationsCount(modelName) { const select = {}; for (const [fieldName, fieldDef] of Object.entries(this.fieldsMap[modelName])) { if (fieldDef.kind === "object" && fieldDef.isList) { const relationModelName = fieldDef.type; const relationPermissions = this.permissionsConfig[relationModelName]; if (!relationPermissions.read) { select[fieldName] = false; } else if (relationPermissions.read !== true) { select[fieldName] = { where: await (0, utils_1.resolvePermissionDefinition)(relationPermissions.read, this.context) }; } else { select[fieldName] = true; } } } return select; } async resolveRelationSelect(modelName, select, relationsMetadata, recursiveContext) { return (0, utils_1.mapObjectValues)(select, async ([selectName, selectValue]) => { if (!selectValue) return selectValue; if (selectName === "_count") { const narrowedSelectValue = selectValue; if ((0, utils_1.isObject)(narrowedSelectValue) && (0, utils_1.isObject)(narrowedSelectValue.select)) { const countRecursiveContext = { path: `${recursiveContext.path}.${selectName}` }; return { select: await this.resolveRelationSelect(modelName, narrowedSelectValue.select, relationsMetadata, countRecursiveContext) }; } else { return { select: await this.generateModelRelationsCount(modelName) }; } } const fieldDef = this.fieldsMap[modelName][selectName]; if (fieldDef.kind !== "object") { return selectValue; } const relationModelName = fieldDef.type; const relationFields = this.fieldsMap[relationModelName]; const relationPermissions = this.permissionsConfig[relationModelName]; const relationRecursiveContext = { path: fieldDef.isList ? `${recursiveContext.path}.${selectName}.*` : `${recursiveContext.path}.${selectName}`, }; if (!fieldDef.isList && fieldDef.isRequired) { const narrowedSelectValue = selectValue; if (this.checkRequiredBelongsTo && relationPermissions.read !== true) { const uniqueField = (0, utils_1.getUniqueField)(relationFields); if ((0, utils_1.isObject)(narrowedSelectValue) && (0, utils_1.isObject)(narrowedSelectValue.select) && !narrowedSelectValue.select[uniqueField.name] && !narrowedSelectValue.include) { throw new Error("You must select a primary key or other unique field for the required belongs to relations"); } relationsMetadata.push({ type: "requiredBelongsTo", path: relationRecursiveContext.path, modelName: relationModelName, }); } if ((0, utils_1.isObject)(narrowedSelectValue)) { return { ...narrowedSelectValue, ...(await this.resolveSelectAndInclude(relationModelName, narrowedSelectValue.select, narrowedSelectValue.include, relationsMetadata, relationRecursiveContext)), }; } else { return narrowedSelectValue; } } const narrowedSelectValue = selectValue; if (!relationPermissions.read) { return { where: (0, utils_1.generateImpossibleWhere)(relationFields) }; } else if (relationPermissions.read !== true) { if ((0, utils_1.isObject)(narrowedSelectValue)) { const [selectAndInclude, permissionDefinition] = await Promise.all([ this.resolveSelectAndInclude(relationModelName, narrowedSelectValue.select, narrowedSelectValue.include, relationsMetadata, relationRecursiveContext), (0, utils_1.resolvePermissionDefinition)(relationPermissions.read, this.context), ]); return { ...narrowedSelectValue, ...selectAndInclude, where: (0, utils_1.mergeWhere)(narrowedSelectValue.where, permissionDefinition), }; } else { return { where: await (0, utils_1.resolvePermissionDefinition)(relationPermissions.read, this.context) }; } } else { if ((0, utils_1.isObject)(narrowedSelectValue)) { return { ...narrowedSelectValue, ...(await this.resolveSelectAndInclude(relationModelName, narrowedSelectValue.select, narrowedSelectValue.include, relationsMetadata, relationRecursiveContext)), }; } } return narrowedSelectValue; }); } async resolveSelectAndInclude(modelName, select, include, relationsMetadata, recursiveContext) { if (select) { return { select: await this.resolveRelationSelect(modelName, select, relationsMetadata, recursiveContext) }; } else if (include) { return { include: await this.resolveRelationSelect(modelName, include, relationsMetadata, recursiveContext) }; } else { return {}; } } async resolveCreate(modelName, data) { return (0, utils_1.mapObjectValues)(data, async ([dataName, dataValue]) => { const fieldDef = this.fieldsMap[modelName][dataName]; if (fieldDef.kind !== "object") { return dataValue; } const relationModelName = fieldDef.type; const relationFields = this.fieldsMap[relationModelName]; const relationPermissions = this.permissionsConfig[relationModelName]; const uniqueFields = this.uniqueFieldsMap[relationModelName]; if (fieldDef.isList) { return (0, utils_1.mapObjectValues)(dataValue, async ([actionName, actionValue]) => { if (!actionValue) return actionValue; switch (actionName) { case "create": if (!relationPermissions.create) { throw this.authorizationError; } else { return (0, utils_1.transformValue)(actionValue, async (value) => { return this.resolveCreate(relationModelName, value); }); } case "createMany": if (!relationPermissions.create) { throw this.authorizationError; } break; case "connectOrCreate": if (!relationPermissions.create) { throw this.authorizationError; } else { if (!relationPermissions.read) { return (0, utils_1.transformValue)(actionValue, async (value) => { return { create: await this.resolveCreate(relationModelName, value.create), where: (0, utils_1.mergeWhereUnique)(relationFields, uniqueFields, value.where, (0, utils_1.generateImpossibleWhere)(relationFields)), }; }); } else if (relationPermissions.read !== true) { const permissionDefinition = await (0, utils_1.resolvePermissionDefinition)(relationPermissions.read, this.context); return (0, utils_1.transformValue)(actionValue, async (value) => { return { create: await this.resolveCreate(relationModelName, value.create), where: (0, utils_1.mergeWhereUnique)(relationFields, uniqueFields, value.where, permissionDefinition), }; }); } else { return (0, utils_1.transformValue)(actionValue, async (value) => { return { create: await this.resolveCreate(relationModelName, value), where: value.where, }; }); } } case "connect": if (!relationPermissions.read) { return (0, utils_1.transformValue)(actionValue, async (value) => { return (0, utils_1.mergeWhereUnique)(relationFields, uniqueFields, value, (0, utils_1.generateImpossibleWhere)(relationFields)); }); } else if (relationPermissions.read !== true) { const permissionDefinition = await (0, utils_1.resolvePermissionDefinition)(relationPermissions.read, this.context); return (0, utils_1.transformValue)(actionValue, async (value) => { return (0, utils_1.mergeWhereUnique)(relationFields, uniqueFields, value, permissionDefinition); }); } break; default: throw new Error(`Failed to resolve the operation '${actionName}' in the nested create for a list relation`); } return actionValue; }); } else { return (0, utils_1.mapObjectValues)(dataValue, async ([actionName, actionValue]) => { if (!actionValue) return actionValue; switch (actionName) { case "create": if (!relationPermissions.create) { throw this.authorizationError; } else { return this.resolveCreate(relationModelName, actionValue); } case "connectOrCreate": if (!relationPermissions.create) { throw this.authorizationError; } else { if (!relationPermissions.read) { return { create: await this.resolveCreate(relationModelName, actionValue.create), where: (0, utils_1.mergeWhereUnique)(relationFields, uniqueFields, actionValue.where, (0, utils_1.generateImpossibleWhere)(relationFields)), }; } else if (relationPermissions.read !== true) { const [create, permissionDefinition] = await Promise.all([ this.resolveCreate(relationModelName, actionValue.create), (0, utils_1.resolvePermissionDefinition)(relationPermissions.read, this.context), ]); return { create, where: (0, utils_1.mergeWhereUnique)(relationFields, uniqueFields, actionValue.where, permissionDefinition), }; } else { return { create: await this.resolveCreate(relationModelName, actionValue.create), where: actionValue.where, }; } } case "connect": if (!relationPermissions.read) { return (0, utils_1.mergeWhereUnique)(relationFields, uniqueFields, actionValue, (0, utils_1.generateImpossibleWhere)(relationFields)); } else if (relationPermissions.read !== true) { return (0, utils_1.mergeWhereUnique)(relationFields, uniqueFields, actionValue, await (0, utils_1.resolvePermissionDefinition)(relationPermissions.read, this.context)); } break; default: throw new Error(`Failed to resolve the operation '${actionName}' in the nested create for a non-list relation`); } return actionValue; }); } }); } async resolveUpdate(modelName, data) { return (0, utils_1.mapObjectValues)(data, async ([dataName, dataValue]) => { const fieldDef = this.fieldsMap[modelName][dataName]; if (fieldDef.kind !== "object") { return dataValue; } const relationModelName = fieldDef.type; const relationFields = this.fieldsMap[relationModelName]; const relationPermissions = this.permissionsConfig[relationModelName]; const uniqueFields = this.uniqueFieldsMap[relationModelName]; if (fieldDef.isList) { return (0, utils_1.mapObjectValues)(dataValue, async ([actionName, actionValue]) => { if (!actionValue) return actionValue; switch (actionName) { case "create": if (!relationPermissions.create) { throw this.authorizationError; } else { return (0, utils_1.transformValue)(actionValue, async (value) => { return this.resolveCreate(relationModelName, value); }); } case "createMany": if (!relationPermissions.create) { throw this.authorizationError; } break; case "connectOrCreate": if (!relationPermissions.create) { throw this.authorizationError; } else { if (!relationPermissions.read) { return (0, utils_1.transformValue)(actionValue, async (value) => { return { create: await this.resolveCreate(relationModelName, value.create), where: (0, utils_1.mergeWhereUnique)(relationFields, uniqueFields, value.where, (0, utils_1.generateImpossibleWhere)(relationFields)), }; }); } else if (relationPermissions.read !== true) { const permissionDefinition = await (0, utils_1.resolvePermissionDefinition)(relationPermissions.read, this.context); return (0, utils_1.transformValue)(actionValue, async (value) => { return { create: await this.resolveCreate(relationModelName, value.create), where: (0, utils_1.mergeWhereUnique)(relationFields, uniqueFields, value.where, permissionDefinition), }; }); } else { return (0, utils_1.transformValue)(actionValue, async (value) => { return { create: await this.resolveCreate(relationModelName, value.create), where: value.where, }; }); } } case "set": case "connect": case "disconnect": if (!relationPermissions.read) { return (0, utils_1.transformValue)(actionValue, async (value) => { return (0, utils_1.mergeWhereUnique)(relationFields, uniqueFields, value, (0, utils_1.generateImpossibleWhere)(relationFields)); }); } else if (relationPermissions.read !== true) { const permissionDefinition = await (0, utils_1.resolvePermissionDefinition)(relationPermissions.read, this.context); return (0, utils_1.transformValue)(actionValue, async (value) => { return (0, utils_1.mergeWhereUnique)(relationFields, uniqueFields, value, permissionDefinition); }); } break; case "update": if (!relationPermissions.update) { throw this.authorizationError; } else if (relationPermissions.update !== true) { const permissionDefinition = await (0, utils_1.resolvePermissionDefinition)(relationPermissions.update, this.context); return (0, utils_1.transformValue)(actionValue, async (value) => { return { data: await this.resolveUpdate(relationModelName, value.data), where: (0, utils_1.mergeWhereUnique)(relationFields, uniqueFields, value.where, permissionDefinition), }; }); } else { return (0, utils_1.transformValue)(actionValue, async (value) => { return { data: await this.resolveUpdate(relationModelName, value.data), where: value.where, }; }); } case "updateMany": if (!relationPermissions.update) { throw this.authorizationError; } else if (relationPermissions.update !== true) { const permissionDefinition = await (0, utils_1.resolvePermissionDefinition)(relationPermissions.update, this.context); return (0, utils_1.transformValue)(actionValue, async (value) => { return { data: value.data, where: (0, utils_1.mergeWhere)(value.where, permissionDefinition), }; }); } break; case "upsert": if (!relationPermissions.create || !relationPermissions.update) { throw this.authorizationError; } else if (relationPermissions.update !== true) { const permissionDefinition = await (0, utils_1.resolvePermissionDefinition)(relationPermissions.update, this.context); return (0, utils_1.transformValue)(actionValue, async (value) => { const [create, update] = await Promise.all([ this.resolveCreate(relationModelName, value.create), this.resolveUpdate(relationModelName, value.update), ]); return { create, update, where: (0, utils_1.mergeWhereUnique)(relationFields, uniqueFields, value.where, permissionDefinition), }; }); } else { return (0, utils_1.transformValue)(actionValue, async (value) => { const [create, update] = await Promise.all([ this.resolveCreate(relationModelName, value.create), this.resolveUpdate(relationModelName, value.update), ]); return { create, update, where: value.where }; }); } case "delete": if (!relationPermissions.delete) { throw this.authorizationError; } else if (relationPermissions.delete !== true) { const permissionDefinition = await (0, utils_1.resolvePermissionDefinition)(relationPermissions.delete, this.context); return (0, utils_1.transformValue)(actionValue, async (value) => { return (0, utils_1.mergeWhereUnique)(relationFields, uniqueFields, value, permissionDefinition); }); } break; case "deleteMany": if (!relationPermissions.delete) { throw this.authorizationError; } else if (relationPermissions.delete !== true) { const permissionDefinition = await (0, utils_1.resolvePermissionDefinition)(relationPermissions.delete, this.context); return (0, utils_1.transformValue)(actionValue, async (value) => { return (0, utils_1.mergeWhere)(value, permissionDefinition); }); } break; default: throw new Error(`Failed to resolve the operation '${actionName}' in the nested update for a list relation`); } return actionValue; }); } else { return (0, utils_1.mapObjectValues)(dataValue, async ([actionName, actionValue]) => { if (!actionValue) return actionValue; switch (actionName) { case "create": if (!relationPermissions.create) { throw this.authorizationError; } else { return this.resolveCreate(relationModelName, actionValue); } case "connectOrCreate": if (!relationPermissions.create) { throw this.authorizationError; } else { if (!relationPermissions.read) { return { create: await this.resolveCreate(relationModelName, actionValue.create), where: (0, utils_1.mergeWhereUnique)(relationFields, uniqueFields, actionValue.where, (0, utils_1.generateImpossibleWhere)(relationFields)), }; } else if (relationPermissions.read !== true) { const [create, permissionDefinition] = await Promise.all([ this.resolveCreate(relationModelName, actionValue.create), (0, utils_1.resolvePermissionDefinition)(relationPermissions.read, this.context), ]); return { create, where: (0, utils_1.mergeWhereUnique)(relationFields, uniqueFields, actionValue.where, permissionDefinition), }; } else { return { create: await this.resolveCreate(relationModelName, actionValue.create), where: actionValue.where, }; } } case "connect": if (!relationPermissions.read) { return (0, utils_1.mergeWhereUnique)(relationFields, uniqueFields, actionValue, (0, utils_1.generateImpossibleWhere)(relationFields)); } else if (relationPermissions.read !== true) { return (0, utils_1.mergeWhereUnique)(relationFields, uniqueFields, actionValue, await (0, utils_1.resolvePermissionDefinition)(relationPermissions.read, this.context)); } break; case "disconnect": if (!relationPermissions.read) { return (0, utils_1.generateImpossibleWhere)(relationFields); } else if (relationPermissions.read !== true) { if ((0, utils_1.isObject)(actionValue)) { return (0, utils_1.mergeWhere)(actionValue, await (0, utils_1.resolvePermissionDefinition)(relationPermissions.read, this.context)); } else { return (0, utils_1.resolvePermissionDefinition)(relationPermissions.read, this.context); } } break; case "update": if (!relationPermissions.update) { throw this.authorizationError; } else if (relationPermissions.update !== true) { if (!actionValue.data) { const [data, where] = await Promise.all([ this.resolveUpdate(relationModelName, actionValue), (0, utils_1.resolvePermissionDefinition)(relationPermissions.update, this.context), ]); return { data, where }; } else if (!actionValue.where) { const [data, where] = await Promise.all([ this.resolveUpdate(relationModelName, actionValue.data), (0, utils_1.resolvePermissionDefinition)(relationPermissions.update, this.context), ]); return { data, where }; } else { const [data, permissionDefinition] = await Promise.all([ this.resolveUpdate(relationModelName, actionValue.data), (0, utils_1.resolvePermissionDefinition)(relationPermissions.update, this.context), ]); return { data, where: (0, utils_1.mergeWhereUnique)(relationFields, uniqueFields, actionValue.where, permissionDefinition), }; } } else { if (!actionValue.data) { return this.resolveUpdate(relationModelName, actionValue); } else { return { data: await this.resolveUpdate(relationModelName, actionValue.data), where: actionValue.where, }; } } case "upsert": if (!relationPermissions.create || !relationPermissions.update) { throw this.authorizationError; } else if (relationPermissions.update !== true) { if (!actionValue.where) { const [create, update, where] = await Promise.all([ this.resolveCreate(relationModelName, actionValue.create), this.resolveUpdate(relationModelName, actionValue.update), (0, utils_1.resolvePermissionDefinition)(relationPermissions.update, this.context), ]); return { create, update, where }; } else { const [create, update, permissionDefinition] = await Promise.all([ this.resolveCreate(relationModelName, actionValue.create), this.resolveUpdate(relationModelName, actionValue.update), (0, utils_1.resolvePermissionDefinition)(relationPermissions.update, this.context), ]); return { create, update, where: (0, utils_1.mergeWhereUnique)(relationFields, uniqueFields, actionValue.where, permissionDefinition), }; } } else { const [create, update] = await Promise.all([ this.resolveCreate(relationModelName, actionValue.create), this.resolveUpdate(relationModelName, actionValue.update), ]); return { create, update, where: actionValue.where }; } case "delete": if (!relationPermissions.delete) { throw this.authorizationError; } else if (relationPermissions.delete !== true) { if ((0, utils_1.isObject)(actionValue)) { return (0, utils_1.mergeWhere)(actionValue, await (0, utils_1.resolvePermissionDefinition)(relationPermissions.delete, this.context)); } else { return (0, utils_1.resolvePermissionDefinition)(relationPermissions.delete, this.context); } } break; default: throw new Error(`Failed to resolve the operation '${actionName}' in the nested update for a non-list relation`); } return actionValue; }); } }); } async performRelationProcessing(transactionClient, result, relationsMetadata) { for (const relationMetadata of relationsMetadata) { switch (relationMetadata.type) { case "requiredBelongsTo": const matchingRelations = (0, utils_1.pickByPath)(result, relationMetadata.path); if (!matchingRelations.length) { continue; } const relationPermissions = this.permissionsConfig[relationMetadata.modelName]; if (!relationPermissions.read) { throw new errors_1.ReferentialIntegrityError(); } else if (relationPermissions.read === true) { continue; } const uniqueField = (0, utils_1.getUniqueField)(this.fieldsMap[relationMetadata.modelName]); const uniqueFieldValues = (0, utils_1.uniqueArray)(matchingRelations.map((relation) => relation[uniqueField.name])); const allowedCount = await transactionClient[(0, utils_1.lowerFirst)(relationMetadata.modelName)].count({ where: (0, utils_1.mergeWhere)({ [uniqueField.name]: { in: uniqueFieldValues } }, await (0, utils_1.resolvePermissionDefinition)(relationPermissions.read, this.context)), }); if (uniqueFieldValues.length !== allowedCount) { throw new errors_1.ReferentialIntegrityError(); } break; default: throw new Error(`Failed to resolve the relation metadata type '${relationMetadata.type}' during relation processing`); } } return result; } } exports.ModelResolver = ModelResolver;