prisma-rls
Advanced tools
Comparing version 0.1.0 to 0.1.1
@@ -16,3 +16,3 @@ "use strict"; | ||
return extension_1.Prisma.defineExtension({ | ||
name: "prisma-extension-rls", | ||
name: "prisma-rls", | ||
query: { | ||
@@ -25,3 +25,3 @@ $allModels: { | ||
case "findUnique": | ||
if (!modelPermissions.select) { | ||
if (!modelPermissions.read) { | ||
return Promise.resolve(null); | ||
@@ -32,3 +32,3 @@ } | ||
case "findUniqueOrThrow": | ||
if (!modelPermissions.select) { | ||
if (!modelPermissions.read) { | ||
throw new library_1.PrismaClientKnownRequestError(`No ${modelName} found`, { | ||
@@ -41,3 +41,3 @@ code: "P2025", | ||
case "findMany": | ||
if (!modelPermissions.select) { | ||
if (!modelPermissions.read) { | ||
return Promise.resolve([]); | ||
@@ -47,3 +47,3 @@ } | ||
case "aggregate": | ||
if (!modelPermissions.select) { | ||
if (!modelPermissions.read) { | ||
return query({ | ||
@@ -56,3 +56,3 @@ ...args, | ||
case "count": | ||
if (!modelPermissions.select) { | ||
if (!modelPermissions.read) { | ||
return Promise.resolve(0); | ||
@@ -62,3 +62,3 @@ } | ||
case "groupBy": | ||
if (!modelPermissions.select) { | ||
if (!modelPermissions.read) { | ||
return Promise.resolve([]); | ||
@@ -94,7 +94,7 @@ } | ||
case "findUniqueOrThrow": | ||
if (modelPermissions.select !== true || args.select || args.include) { | ||
if (modelPermissions.read !== true || args.select || args.include) { | ||
return query({ | ||
...args, | ||
...(0, utils_1.mergeSelectAndInclude)(permissionsConfig, context, fieldsMap, modelName, args.select, args.include), | ||
where: (0, utils_1.mergeWhereUnique)(fieldsMap, modelName, args.where, (0, utils_1.resolveWhere)(modelPermissions.select, context)), | ||
where: (0, utils_1.mergeWhereUnique)(fieldsMap, modelName, args.where, (0, utils_1.resolveWhere)(modelPermissions.read, context)), | ||
}); | ||
@@ -106,7 +106,7 @@ } | ||
case "findMany": | ||
if (modelPermissions.select !== true || args.select || args.include) { | ||
if (modelPermissions.read !== true || args.select || args.include) { | ||
return query({ | ||
...args, | ||
...(0, utils_1.mergeSelectAndInclude)(permissionsConfig, context, fieldsMap, modelName, args.select, args.include), | ||
where: (0, utils_1.mergeWhere)(args.where, (0, utils_1.resolveWhere)(modelPermissions.select, context)), | ||
where: (0, utils_1.mergeWhere)(args.where, (0, utils_1.resolveWhere)(modelPermissions.read, context)), | ||
}); | ||
@@ -118,6 +118,6 @@ } | ||
case "groupBy": | ||
if (modelPermissions.select !== true) { | ||
if (modelPermissions.read !== true) { | ||
return query({ | ||
...args, | ||
where: (0, utils_1.mergeWhere)(args.where, (0, utils_1.resolveWhere)(modelPermissions.select, context)), | ||
where: (0, utils_1.mergeWhere)(args.where, (0, utils_1.resolveWhere)(modelPermissions.read, context)), | ||
}); | ||
@@ -124,0 +124,0 @@ } |
@@ -8,3 +8,3 @@ import type { DMMF } from "@prisma/client/runtime/library"; | ||
export type ModelPermissionsConfig<TypeMap extends PrismaTypeMap, ModelName extends PrismaModelName<TypeMap>, Context extends unknown> = { | ||
select: boolean | PrismaModelWhere<TypeMap, ModelName> | ((context: Context) => PrismaModelWhere<TypeMap, ModelName>); | ||
read: boolean | PrismaModelWhere<TypeMap, ModelName> | ((context: Context) => PrismaModelWhere<TypeMap, ModelName>); | ||
create: boolean; | ||
@@ -11,0 +11,0 @@ update: boolean | PrismaModelWhere<TypeMap, ModelName> | ((context: Context) => PrismaModelWhere<TypeMap, ModelName>); |
@@ -66,3 +66,3 @@ "use strict"; | ||
const relationPermissions = permissionsConfig[relationModelName]; | ||
select[fieldName] = { where: (0, exports.resolveWhere)(relationPermissions.select, context) }; | ||
select[fieldName] = { where: (0, exports.resolveWhere)(relationPermissions.read, context) }; | ||
} | ||
@@ -89,13 +89,13 @@ } | ||
const relationPermissions = permissionsConfig[relationModelName]; | ||
if (relationPermissions.select === false) { | ||
if (relationPermissions.read === false) { | ||
return { where: (0, exports.generateImpossibleWhere)(fieldsMap[modelName]) }; | ||
} | ||
else if (relationPermissions.select !== true && selectValue === true) { | ||
return { where: (0, exports.resolveWhere)(relationPermissions.select, context) }; | ||
else if (relationPermissions.read !== true && selectValue === true) { | ||
return { where: (0, exports.resolveWhere)(relationPermissions.read, context) }; | ||
} | ||
else if (relationPermissions.select !== true && selectValue !== false) { | ||
else if (relationPermissions.read !== true && selectValue !== false) { | ||
return { | ||
...selectValue, | ||
...(0, exports.mergeSelectAndInclude)(permissionsConfig, context, fieldsMap, relationModelName, selectValue.select, selectValue.include), | ||
where: (0, exports.mergeWhere)(selectValue.where, (0, exports.resolveWhere)(relationPermissions.select, context)), | ||
where: (0, exports.mergeWhere)(selectValue.where, (0, exports.resolveWhere)(relationPermissions.read, context)), | ||
}; | ||
@@ -102,0 +102,0 @@ } |
{ | ||
"name": "prisma-rls", | ||
"version": "0.1.0", | ||
"version": "0.1.1", | ||
"description": "Prisma client extension for row-level security on any database", | ||
@@ -11,6 +11,7 @@ "license": "MIT", | ||
"prisma-extension", | ||
"rls" | ||
"rls", | ||
"row-level-security" | ||
], | ||
"author": "s1owjke", | ||
"homepage": "https://github.com/s1owjke/prisma-extension-rls", | ||
"homepage": "https://github.com/s1owjke/prisma-rls", | ||
"main": "dist/index.js", | ||
@@ -17,0 +18,0 @@ "types": "dist/index.d.ts", |
# Prisma RLS | ||
> 🚧 It's an experimental package, under development | ||
[](https://www.npmjs.com/package/prisma-rls) [](https://opensource.org/licenses/MIT) | ||
The classical approach to implementing Row-Level Security (RLS) involves using a database with built-in support and writing row-level security policies for tables. | ||
> 🚧 The package is under active development, public api could be changed | ||
The classic approach to implementing Row-Level Security (RLS) requires a database with built-in support and writing row-level security policies for tables. | ||
This library offers an alternative approach - an extension to the Prisma client that adds additional "where" clauses to all model queries. This approach doesn't require RLS support on the database side (for example, in MySQL). | ||
It's important to keep in mind that this extension doesn't cover raw queries. In such cases, you should take care of it yourself. | ||
It's important to keep in mind that this extension doesn't cover raw queries. In such cases, you should take care of it yourself or use classic approach. | ||
## How to use it | ||
Extend the Prisma client with the rls extension | ||
Define permissions for all models in your schema. | ||
```typescript | ||
import { Prisma } from "@prisma/client"; | ||
import { PermissionsConfig } from "prisma-rls"; | ||
export type Context = Record<string, any>; | ||
type RolePermissions = PermissionsConfig<Prisma.TypeMap, Context>; | ||
export const Guest: RolePermissions = { | ||
Post: { | ||
read: { published: { equals: true } }, | ||
create: false, | ||
update: false, | ||
delete: false, | ||
}, | ||
User: { | ||
read: { role: { not: { equals: "admin" } } }, | ||
create: false, | ||
update: false, | ||
delete: false, | ||
}, | ||
} | ||
``` | ||
Extend the Prisma client with the rls extension. | ||
```typescript | ||
import { Prisma, PrismaClient } from "@prisma/client"; | ||
import { createRlsExtension } from "prisma-extension-rls"; | ||
import { createRlsExtension } from "prisma-rls"; | ||
import { permissions } from "./permissions"; | ||
import { Guest } from "./permissions/guest"; | ||
const db = new PrismaClient().$extends(createRlsExtension(Prisma.dmmf, permissions, null)); | ||
const context: Context = {}; | ||
const db = new PrismaClient().$extends(createRlsExtension(Prisma.dmmf, Guest, context)); | ||
``` | ||
Since Prisma doesn't support contexts to pass data to the [extensions](https://www.prisma.io/docs/orm/prisma-client/client-extensions), you will typically extend the Prisma client per request (based on current auth toke role) | ||
After that all requests except raw will be executed according to permissions | ||
### Permissions registry | ||
Almost always you will have several roles, to describe them in a type-safe way use the following pattern: | ||
```typescript | ||
import { admin } from "./admin"; | ||
import { guest } from "./guest"; | ||
type Role = "Admin" | "Guest"; | ||
type PermissionsRegistry = Record<Role, RolePermissions>; | ||
export const permissionsRegistry = { | ||
Admin: admin, | ||
Guest: guest, | ||
} satisfies PermissionsRegistry; | ||
``` | ||
### Context | ||
Since Prisma doesn't support context to pass data to the [extensions](https://www.prisma.io/docs/orm/prisma-client/client-extensions), you will typically extend the client per request (based on role associated with auth token): | ||
```typescript | ||
import { Prisma, PrismaClient } from "@prisma/client"; | ||
import Fastify, { FastifyRequest } from "fastify"; | ||
import { createRlsExtension } from "prisma-extension-rls"; | ||
import { createRlsExtension } from "prisma-rls"; | ||
import { permissions } from "./permissions"; | ||
import { permissionsRegistry } from "./permissions"; | ||
@@ -39,3 +88,3 @@ (async () => { | ||
const role = resolveRole(request.headers.authorization); | ||
const rolePermissions = permissions[role]; | ||
const rolePermissions = permissionsRegistry[role]; | ||
@@ -42,0 +91,0 @@ return { db: prisma.$extends(createRlsExtension(Prisma.dmmf, rolePermissions, { role })) }; |
23063
101