-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
23 changed files
with
4,627 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
node_modules | ||
dev.db | ||
*.env* | ||
pnpm-lock.yaml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
# Prisma Extension CASL | ||
|
||
|
||
|
||
[Prisma client extension](https://www.prisma.io/docs/orm/prisma-client/client-extensions) that utilizes [CASL](https://casl.js.org/) to enforce authorization logic on most queries. | ||
|
||
> Please be very careful using this library in production! Test your endpoints on your own and raise an issue if some case is not supported by this library! | ||
Supports mainly/only CRUD actions `create`, `read`, `update` and `delete`, which allows us to generate and transform `include`, `select` and `where` queries to enforce nested filtering. | ||
Mutating queries will throw errors in a similar format as CASL. `It's not allowed to "update" "email" on "User"`. | ||
|
||
### Examples | ||
|
||
Now how does it work? | ||
|
||
```ts | ||
const { can, build } = abilityBuilder() | ||
can('read', 'Post', { | ||
thread: { | ||
creatorId: 0 | ||
} | ||
}) | ||
can('read', 'Thread', 'id') | ||
const caslClient = prismaClient.$extends( | ||
useCaslAbilities(build()) | ||
) | ||
const result = await caslClient.post.findMany({ | ||
include: { | ||
thread: true | ||
} | ||
}) | ||
/** | ||
* creates a query under the hood with assistance of @casl/prisma | ||
* | ||
* and even adds a proper select query to thread | ||
* | ||
*{ | ||
* where: { | ||
* AND: [{ | ||
* OR: [{ | ||
* thread: { | ||
* creatorId: 0 | ||
* } | ||
* }] | ||
* }] | ||
* } | ||
* include: { | ||
* thread: { | ||
* select: { | ||
* id: true | ||
* } | ||
* } | ||
* } | ||
* } | ||
*/ | ||
``` | ||
Mutations will only run, if abilities allow it. | ||
|
||
```ts | ||
const { can, build } = abilityBuilder() | ||
can('update', 'Post') | ||
cannot('update', 'Post', 'text') | ||
const caslClient = prismaClient.$extends( | ||
useCaslAbilities(build()) | ||
) | ||
const result = await caslClient.post.update({ data: { text: '-' }, where: { id: 0 }}) | ||
/** | ||
* will throw an error | ||
* because update on text is not allowed | ||
*/ | ||
``` | ||
|
||
Check out tests for some other examples. | ||
|
||
|
||
### Limitations | ||
|
||
- A limitation is the necessary use of `create`, `read`, `update` and `delete` as actions for nested queries. Since this allows us to deal with nested creations or updates. However there is an option to specify a custom `caslAction` for the highest query. It has no typing and is not tested yet. | ||
|
||
```ts | ||
client.user.findUnique({ where: { id: 0 }, caslAction: 'customAction' }) | ||
``` | ||
|
||
- When using prisma probably no one will use columns named `data`, `create`, `update` or `where`. However, if this should be the case, then this library most probably won't work. | ||
|
||
- Currently the following case is not supported. It is still possible to read `email`. Waithing for reply [here](https://github.com/stalniy/casl/discussions/948). | ||
|
||
```ts | ||
can('read', 'User', { | ||
id: 1 | ||
}) | ||
cannot('read', 'User', 'email', { | ||
id: 0 | ||
}) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import * as _prisma_client_runtime_library from '@prisma/client/runtime/library'; | ||
import { Prisma } from '@prisma/client'; | ||
import { PureAbility, AbilityTuple } from '@casl/ability'; | ||
import { PrismaQuery } from '@casl/prisma'; | ||
|
||
type DefaultCaslAction = "create" | "read" | "update" | "delete"; | ||
type PrismaCaslOperation = 'create' | 'createMany' | 'createManyAndReturn' | 'upsert' | 'findFirst' | 'findFirstOrThrow' | 'findMany' | 'findUnique' | 'findUniqueOrThrow' | 'aggregate' | 'count' | 'groupBy' | 'update' | 'updateMany' | 'delete' | 'deleteMany'; | ||
declare const caslOperationDict: Record<PrismaCaslOperation, { | ||
action: DefaultCaslAction; | ||
dataQuery: boolean; | ||
whereQuery: boolean; | ||
includeSelectQuery: boolean; | ||
}>; | ||
declare const useCaslAbilities: (abilities: PureAbility<AbilityTuple, PrismaQuery>) => (client: any) => { | ||
$extends: { | ||
extArgs: _prisma_client_runtime_library.InternalArgs<unknown, unknown, {}, unknown>; | ||
}; | ||
}; | ||
declare function applyCaslToQuery(operation: any, args: any, abilities: PureAbility<AbilityTuple, PrismaQuery>, model: Prisma.ModelName): any; | ||
declare function applyDataQuery(abilities: PureAbility<AbilityTuple, PrismaQuery>, args: any, action: string, model: string): any; | ||
declare function applyWhereQuery(abilities: PureAbility<AbilityTuple, PrismaQuery>, args: any, action: string, model: string, relation?: string): any; | ||
declare const applySelectPermittedFields: (abilities: PureAbility<AbilityTuple, PrismaQuery>, args: any, model: string) => any; | ||
declare const applyIncludeSelectQuery: (abilities: PureAbility<AbilityTuple, PrismaQuery>, args: any, model: string) => any; | ||
declare function capitalizeFirstLetter(string: string): string; | ||
declare function isSubset(obj1: any, obj2: any): boolean; | ||
|
||
export { type PrismaCaslOperation, applyCaslToQuery, applyDataQuery, applyIncludeSelectQuery, applySelectPermittedFields, applyWhereQuery, capitalizeFirstLetter, caslOperationDict, isSubset, useCaslAbilities }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import * as _prisma_client_runtime_library from '@prisma/client/runtime/library'; | ||
import { Prisma } from '@prisma/client'; | ||
import { PureAbility, AbilityTuple } from '@casl/ability'; | ||
import { PrismaQuery } from '@casl/prisma'; | ||
|
||
type DefaultCaslAction = "create" | "read" | "update" | "delete"; | ||
type PrismaCaslOperation = 'create' | 'createMany' | 'createManyAndReturn' | 'upsert' | 'findFirst' | 'findFirstOrThrow' | 'findMany' | 'findUnique' | 'findUniqueOrThrow' | 'aggregate' | 'count' | 'groupBy' | 'update' | 'updateMany' | 'delete' | 'deleteMany'; | ||
declare const caslOperationDict: Record<PrismaCaslOperation, { | ||
action: DefaultCaslAction; | ||
dataQuery: boolean; | ||
whereQuery: boolean; | ||
includeSelectQuery: boolean; | ||
}>; | ||
declare const useCaslAbilities: (abilities: PureAbility<AbilityTuple, PrismaQuery>) => (client: any) => { | ||
$extends: { | ||
extArgs: _prisma_client_runtime_library.InternalArgs<unknown, unknown, {}, unknown>; | ||
}; | ||
}; | ||
declare function applyCaslToQuery(operation: any, args: any, abilities: PureAbility<AbilityTuple, PrismaQuery>, model: Prisma.ModelName): any; | ||
declare function applyDataQuery(abilities: PureAbility<AbilityTuple, PrismaQuery>, args: any, action: string, model: string): any; | ||
declare function applyWhereQuery(abilities: PureAbility<AbilityTuple, PrismaQuery>, args: any, action: string, model: string, relation?: string): any; | ||
declare const applySelectPermittedFields: (abilities: PureAbility<AbilityTuple, PrismaQuery>, args: any, model: string) => any; | ||
declare const applyIncludeSelectQuery: (abilities: PureAbility<AbilityTuple, PrismaQuery>, args: any, model: string) => any; | ||
declare function capitalizeFirstLetter(string: string): string; | ||
declare function isSubset(obj1: any, obj2: any): boolean; | ||
|
||
export { type PrismaCaslOperation, applyCaslToQuery, applyDataQuery, applyIncludeSelectQuery, applySelectPermittedFields, applyWhereQuery, capitalizeFirstLetter, caslOperationDict, isSubset, useCaslAbilities }; |
Oops, something went wrong.