@cerbos/orm-prisma
TypeScript icon, indicating that this package has built-in type declarations

1.1.1 • Public • Published

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 // a filter to pass to the findMany() function of Prisma
}

where PlanKind is:

export enum PlanKind {
  /**
   * The specified action is always allowed for the principal on resources matching the input.
   */
  ALWAYS_ALLOWED = "KIND_ALWAYS_ALLOWED",

  /**
   * The specified action is always denied for the principal on resources matching the input.
   */
  ALWAYS_DENIED = "KIND_ALWAYS_DENIED",

  /**
   * The specified action is conditionally allowed for the principal on resources matching the input.
   */
  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 });

// Fetch the query plan from Cerbos passing in the principal
// resource type and action
const queryPlan = await cerbos.planResources({
  principal: {....},
  resource: { kind: "resourceKind" },
  action: "view"
})

// Generate the prisma filter from the query plan
const result = queryPlanToPrisma({
  queryPlan,
  fieldNameMapper: {
    "request.resource.attr.aFieldName": "prismaModelFieldName"
  },
  relationMapper: {
    "request.resource.attr.aRelatedModel": {
      "relation": "aRelatedModel",
      "field": "id" // the column it is joined on
    }
  }
});

// The query plan says the user would always be denied
// return empty or throw an error depending on your app.
if(result.kind == PlanKind.ALWAYS_DENIED) {
  return console.log([]);
}

// Pass the filters in as where conditions
// If you have prexisting where conditions, you can pass them in an AND clause
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",
  },
});

//or

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", // the column it is joined on
    },
  },
});

//or

const filters = queryPlanToPrisma({
  queryPlan,
  fieldNameMapper: {},
  relationMapper: (fieldName: string): string => {
    if (fieldName.indexOf("request.resource.") > 0) {
      return {
        relation: fieldName.replace("request.resource.attr.", ""),
        field: "id", // the column it is joined on
      };
    }
  },
});

Full Example

A full Prisma application making use of this adapater can be found at https://github.com/cerbos/express-prisma-cerbos

Readme

Keywords

none

Package Sidebar

Install

npm i @cerbos/orm-prisma

Homepage

cerbos.dev

Weekly Downloads

3,923

Version

1.1.1

License

Apache-2.0

Unpacked Size

17.3 kB

Total Files

6

Last publish

Collaborators

  • ahaines
  • alexolivier