Prisma Row-Level Security Extension

0.5.6 · active · verified Wed Apr 22

prisma-rls is a Prisma client extension designed to implement row-level security (RLS) on any database, including those without native RLS support (e.g., MySQL). It achieves this by automatically injecting 'where' clauses into all Prisma model queries, effectively filtering data based on defined permissions. The current stable version is `0.5.6`. The package shows active development with frequent minor releases and bug fixes, indicated by the recent changelog entries. A key differentiator is its database-agnostic approach, allowing RLS where it wouldn't natively exist, and its integration directly into the Prisma client query pipeline. It ships with TypeScript types, providing a type-safe way to define permissions configurations and contexts. It's crucial to note that this extension does not apply to raw database queries, which require manual handling.

Common errors

Warnings

Install

Imports

Quickstart

This example demonstrates how to integrate `prisma-rls` with a Fastify server to apply row-level security based on user roles and context. It defines permissions for 'User' and 'Guest' roles, creates a dynamic Prisma client extension per request, and applies RLS to `Post` and `User` model queries. Authorization is simulated via a bearer token.

import { Prisma, PrismaClient } from "@prisma/client";
import Fastify, { FastifyRequest } from "fastify";
import { createRlsExtension, PermissionsConfig } from "prisma-rls";

// Define shared types for roles and permissions context
export type Role = "User" | "Guest";
export type PermissionsContext = { userId: string | null };
export type RolePermissions = PermissionsConfig<Prisma.TypeMap, PermissionsContext>;
export type PermissionsRegistry = Record<Role, RolePermissions>;

// Define user permissions
const userPermissions: RolePermissions = {
  Post: {
    read: { published: { equals: true } },
    create: true,
    update: (ctx) => ({ authorId: { equals: ctx.userId } }),
    delete: (ctx) => ({ authorId: { equals: ctx.userId } })
  },
  User: {
    read: (ctx) => ({ id: { equals: ctx.userId } }),
    create: false,
    update: (ctx) => ({ id: { equals: ctx.userId } }),
    delete: false
  }
};

// Define guest permissions
const guestPermissions: RolePermissions = {
  Post: {
    read: { published: { equals: true } },
    create: false,
    update: false,
    delete: false
  },
  User: {
    read: false,
    create: false,
    update: false,
    delete: false
  }
};

// Combine permissions into a registry
export const permissionsRegistry = {
  User: userPermissions,
  Guest: guestPermissions
} satisfies PermissionsRegistry;

(async () => {
  const prisma = new PrismaClient();
  const server = Fastify();

  // Dummy function to resolve user from auth header
  const resolveUser = async (authorizationHeader?: string | string[] | undefined) => {
    if (authorizationHeader === 'Bearer user-token') {
      return { id: 'user-123', role: 'User' };
    }
    return null;
  };

  server.decorateRequest('db', null);

  server.addHook('onRequest', async (request: any, reply) => {
    const user = await resolveUser(request.headers.authorization);
    const userRole: Role = user ? user.role : "Guest";
    const permissionsContext: PermissionsContext = { userId: user?.id ?? null };

    const rlsExtension = createRlsExtension({
      dmmf: Prisma.dmmf,
      permissionsConfig: permissionsRegistry[userRole],
      context: permissionsContext,
    });
    request.db = prisma.$extends(rlsExtension);
  });

  server.get("/posts", async function handler(request: any, reply) {
    // Assuming a user with 'user-token' can only see their own posts
    // and public posts. Guests can only see public posts.
    return await request.db.post.findMany();
  });

  server.get("/profile", async function handler(request: any, reply) {
    // A user can only see their own profile, guests see nothing.
    return await request.db.user.findMany(); // Will apply RLS based on `userId`
  });

  await server.listen({ port: 8080, host: "0.0.0.0" });
  console.log('Server listening on http://0.0.0.0:8080');
})();

view raw JSON →