prisma-rls
Version:
Prisma client extension for row-level security on any database
636 lines (635 loc) • 38.1 kB
JavaScript
"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;