Cerbos + Prisma ORM Adapter
An adapter library that takes a Cerbos Query Plan (PlanResources API) response and converts it into a Prisma where class object. This is designed to work alongside a project using the Cerbos Javascript SDK.
The following conditions are supported: and
, or
, eq
, ne
, lt
, gt
, lte
, gte
and in
, as well as relational filters some
, none
, is
and isNot
.
Not Supported:
every
contains
search
mode
startsWith
endsWith
isSet
- Scalar filters
- Atomic number operations
- Composite keys
Requirements
- Cerbos > v0.16
@cerbos/http
or @cerbos/grpc
client
Usage
npm install @cerbos/orm-prisma
This package exports a function:
import { queryPlanToPrisma, PlanKind } from "@cerbos/orm-prisma";
queryPlanToPrisma({ queryPlan, fieldNameMapper, relationMapper }): {
kind: PlanKind,
filters?: any
}
where PlanKind
is:
export enum PlanKind {
ALWAYS_ALLOWED = "KIND_ALWAYS_ALLOWED",
ALWAYS_DENIED = "KIND_ALWAYS_DENIED",
CONDITIONAL = "KIND_CONDITIONAL",
}
The function reqiures the full query plan from Cerbos to be passed in an object along with a fieldNameMapper
and option relationMapper
if the model has relations.
A basic implementation can be as simple as:
import { GRPC as Cerbos } from "@cerbos/grpc";
import { PrismaClient } from "@prisma/client";
import { queryPlanToPrisma, PlanKind } from "@cerbos/orm-prisma";
const prisma = new PrismaClient();
const cerbos = new Cerbos("localhost:3592", { tls: false });
const queryPlan = await cerbos.planResources({
principal: {....},
resource: { kind: "resourceKind" },
action: "view"
})
const result = queryPlanToPrisma({
queryPlan,
fieldNameMapper: {
"request.resource.attr.aFieldName": "prismaModelFieldName"
},
relationMapper: {
"request.resource.attr.aRelatedModel": {
"relation": "aRelatedModel",
"field": "id"
}
}
});
if(result.kind == PlanKind.ALWAYS_DENIED) {
return console.log([]);
}
const result = await prisma.myModel.findMany({
where: {
AND: result.filters
},
});
console.log(result)
The fieldNameMapper
is used to convert the field names in the query plan response to names of fields in the Prisma model - this can be done as a map or a function:
const filters = queryPlanToPrisma({
queryPlan,
fieldNameMapper: {
"request.resource.attr.aFieldName": "prismaModelFieldName",
},
});
const filters = queryPlanToPrisma({
queryPlan,
fieldNameMapper: (fieldName: string): string => {
if (fieldName.indexOf("request.resource.") > 0) {
return fieldName.replace("request.resource.attr", "");
}
if (fieldName.indexOf("request.principal.") > 0) {
return fieldName.replace("request.principal.attr", "");
}
},
});
The relationMapper
is used to convert references to fields which are joins at the database level
const filters = queryPlanToPrisma({
queryPlan,
fieldNameMapper: {},
relationMapper: {
"request.resource.attr.aRelatedModel": {
relation: "aRelatedModel",
field: "id",
},
},
});
const filters = queryPlanToPrisma({
queryPlan,
fieldNameMapper: {},
relationMapper: (fieldName: string): string => {
if (fieldName.indexOf("request.resource.") > 0) {
return {
relation: fieldName.replace("request.resource.attr.", ""),
field: "id",
};
}
},
});
Full Example
A full Prisma application making use of this adapater can be found at https://github.com/cerbos/express-prisma-cerbos