npm package: @prisma/extension-rules
Description: Prisma Client extension for Policy
Latest version: 0.3.6

# @prisma/extension-rules

## Permission rules for Prisma Postgres

Prisma Security Rules is a **permission rules system for Prisma ORM and Prisma Postgres**. With Security Rules, you can:

- define access permission rules in TypeScript
- deploy the permission rules to your Prisma Postgres instance
- query Prisma Postgres with the permission rules applied
- access Prisma Postgres from the frontend

At the core of it, you define rules that allow/deny requests to your database based on model name, operation (e.g. `findMany`) and the parameters you pass to said operation.

## Usage

### Prerequisites

- A project with Prisma ORM (>=6.2.0) installed and a Prisma Postgres database. If you don’t have one, you can get started [here](https://www.prisma.io/docs/getting-started/prisma-postgres/from-the-cli?utm_source=readme&utm_medium=rules_extension).

- The Prisma CLI being authenticated with Prisma Data Platform (via [`prisma platform auth login --early-access`](https://www.prisma.io/docs/platform/platform-cli/commands?utm_source=readme&utm_medium=rules_extension#auth-login)).

### 1. Install the Prisma Client extension for Policy

```shell
npm install @prisma/extension-rules
```

<details>
  <summary>
    Expand to see installation instructions for `pnpm`, `yarn` and `bun`
  </summary>

#### pnpm

```shell
pnpm add @prisma/extension-rules
```

#### Yarn

```shell
yarn add @prisma/extension-rules
```

#### Bun

```shell
bun install @prisma/extension-rules
```

</details>

### 2. Extend your `PrismaClient` with the Policy extension

First, import the extension and add it to your existing Prisma Client:

```ts
import { PrismaClient } from "@prisma/client";
import { withPolicy } from "@prisma/extension-rules";
import { z } from "zod";

import { decode } from "path/to/decode"; // imaginary import of your jwt token decode helper

export const prisma = new PrismaClient().$extends(
  withPolicy({
    contextSchema: z.object({
      token: z.string(),
    }),
    rules: {
      user: {
        async read(req) {
          // On each request, the context object can be used to perform custom auth/access business logic. We know it's shape thanks to the `contextSchema` property.
          const { userId } = await decode(req.context.token);

          return {
            $where: { id: userId },
          };
        },
        $allOperations: false,
      },
      $allModels: false,
      $transaction: false,
    },
  }),
);
```

> Note: While the rules are applied to your `PrismaClient` instance, this instance will retain _full_ database access.

This example uses `zod`, but you can use any schema library that implements the [standard schema](https://github.com/standard-schema/standard-schema#what-schema-libraries-implement-the-spec) specification.

This example includes Policy rules that will deny all requests except for reads (e.g. `findMany`) on `user` model. It also ensures a user only sees their own data by applying a `where` override (effectively `AND(incoming_query_where, rule_$where)`) that filters by the user's id (decoded from the session token).

### 3. Create a file exporting the `PolicyClient` for your application

```ts
import { PolicyClient } from "@prisma/extension-rules";

// it is very important to not forget the `type` annotation here, to tell TypeScript to only import the types!!!
import type { prisma as rpcClient } from "path/to/file/where/rules/are/used";

export const prisma = new PolicyClient<typeof rpcClient>({
  publicKey: "<TBD>",
});
```

### 4. Deploy Policy rules

```shell
prisma rules deploy <rules_name> -f path/to/file/where/rules/are/used
```

Be sure to copy the public key from the deploy command's output in your terminal, and replace the `<TBD>` from the previous step.

### 5. Use `PolicyClient` to access Prisma Postgres

`PolicyClient` is a lightweight version of `PrismaClient` that you can use in your browser. Once your rules are deployed, you can use it as follows in your application:

```ts
import { prisma } from "path/to/rules-client";

prisma.setGlobalContext({ token: "<some_session_token>" });

const users = await prisma.users.findMany();
```

## How it works

This package exposes:

- A [Prisma Client extension](https://www.prisma.io/docs/orm/prisma-client/client-extensions) called `withPolicy` that helps attach Prisma Policy-related configuration to an existing Prisma ORM client, making it suitable to act as a Remote Procedure Call (RPC) called object once bundled and deployed to Prisma Policy's workers.

- A lightweight Remote Procedure Call (RPC) stub called `PolicyClient` that allows your application to execute Prisma ORM queries and transactions remotely on Prisma Policy's workers in a secure way (based on your Policy configuration).

The deployment logic is not part of this package, and is accessible via the Prisma CLI through `prisma rules`. A deployment's output will include a public key you'll need to pass to `PolicyClient`.

Here's a simplified high-level view of Policy's architecture:

![Prisma Policy's RPC in excalidraw](https://www.unpkg.com/@prisma/extension-rules@latest/assets/rpc.png)

### Rule Engine

Each request, that is received by Policy's workers, will be evaluated by a rule engine:

- if a request does not adhere to your rules, it will be denied with a reason, and the `PolicyClient` will throw an error.

- if a request adheres to your rules, it will be executed with the `PrismaClient` and the results will be sent to the `PolicyClient`.

#### Rules

Rules are defined with a JavaScript plain nested object.

The root object is defined as:

```ts
{
  // model name must exist in your `PrismaClient`'s schema.
  // when your schema changes, make sure to redeploy your rules,
  // to avoid instant denies from the engine for requests that access new models.
  [modelName: string]?: ModelRules;
  // fallback in case the request's model name has no specific rules.
  $allModels?: ModelRules;
  // allow or deny transactions.
  $transaction?: boolean;
}
```

Model rules are defined as follows:

```ts
type ModelRules =
  // simple allow/deny toggle.
  | boolean
  // more nuanced rules.
  | {
      // rules for all create operations.
      create?: OperationRules;
      // rules for all read operations.
      read?: OperationRules;
      // rules for all update operations.
      update?: OperationRules;
      // rules for all delete operations.
      delete?: OperationRules;
      // fallback in case the request's operation group doesn't have specific rules.
      $allOperations?: OperationRules;
      // will deny requests if at least one of these fields is in the request's
      // arguments or is returned by the query. If an operation has specific
      // $blockedFields, they will override this property.
      // Example: ['password']
      $blockedFields?: string[];
    };
```

Operation rules are best described by the following diagram:

![Prisma Policy's operation rules "onion"](https://www.unpkg.com/@prisma/extension-rules@latest/assets/operation-rules.png)

It's like an 🧅.

`$where` overrides the incoming request's filtering by `AND`ing any existing filters with it.

The callback's `req` argument is an object that includes the model name, operation, args object, and the global context object.

`$rule` is a verbose way of defining both an access rule and additional rules/logic for a specific operation:

- `$before` allows running custom code before the query is executed by `PrismaClient`.

- `$after` allows running custom code after the query is executed by `PrismaClient`.

- `$blockedFields` behaves similarly to the one that can be defined at the model level, but overrides it if it exists.

#### Rule Evaluation

Here's a diagram depicting the rule engine's evaluation process in a simplified manner:

![Prisma Policy's rule engine algorithm](https://www.unpkg.com/@prisma/extension-rules@latest/assets/rule-engine.png)

The rule engine is designed to introduce as little overhead as possible. As soon as a rule is not adhered by some check, the request is denied. Meaning, it doesn't try and run additional checks to collect more deny reasons.

## Need help?

If you need assistance, reach out in the #help-and-questions channel on our [Discord](https://pris.ly/discord), or connect with our community to see how others are using Policy.