Hello, I am sorry if the question is dumb as I am pretty new here. I am not sure if I have done correctly.
I am using prisma with nestjs, and want to add dynamic permissions using casl. As far as I understand from the docs, json defined rules would be my choice as I want to manage and assign the permissions to users via dashboard.
So according to https://casl.js.org/v5/en/cookbook/roles-with-persisted-permissions, I would choose the second option to build a permission model in prisma like this:
enum Action {
Manage
Create
Update
Read
Delete
}
model Permission {
id String @id @default(cuid())
action Action
subject String
fields String[]
conditions Json?
inverted Boolean? @default(false)
reason String?
role Role @relation(fields: [roleId], references: [id])
roleId String
}
and use prisma's methods to CRUD rules:
permissions.service.ts
async readPermissions(query: Prisma.PermissionFindManyArgs): Promise<Permission[]> {
return await this.prisma.permission.findMany(query)
}
async createPermission(data: Prisma.PermissionCreateInput): Promise<Permission> {
return await this.prisma.permission.create({
data,
})
}
...
and need to dynamically generate ability instance with the defined rules for different prisma models.
according to https://docs.nestjs.com/security/authorization#integrating-casl and the above cookbook:
@Injectable()
export class AbilityFactory {
constructor(private permissionsService: PermissionsService) {}
async createAbility(modelName: Prisma.ModelName) {
if (modelName === 'Permission') throw new HttpException('Error', HttpStatus.CONFLICT)
const subjects = [modelName, 'all']
const actions = Object.keys(Action)
type Abilities = [
typeof actions[number],
typeof subjects[number] | ForcedSubject<Exclude<typeof subjects[number], 'all'>>,
]
// type AppAbility = Ability<Abilities>
// get the defined rules(permissions) via prisma
const permissions = await this.permissionsService.readPermissions({
where: {
subject: modelName,
},
select: {
action: true,
subject: true,
fields: true,
conditions: true,
inverted: true,
reason: true,
role: true,
roleId: true,
},
})
return new Ability<Abilities>(permissions)
}
}
here when I found the @casl/prisma package, I could barely understand its usage from the docs.
So my question is that, is this correct if I use the @casl/prisma package instead of the above to generate a prisma specific ability instance, and pass the ability to accessibleBy
in prisma methods?
the new ability builder would look like this, where the subject type conflicts :
async createPrismaAbility() {
// need to import all models
type PrismaModels = Subjects<{
Address: Address
Comment: Comment
Country: Country
File: File
...
User: User
}>
type AppAbility = PrismaAbility<[string, PrismaModels]>
const AppAbility = PrismaAbility as AbilityClass<AppAbility>
// return new Ability<AppAbility>(rules)
const { can, cannot, build } = new AbilityBuilder(AppAbility)
// get all defined rules
const permissions = await this.permissionsService.readPermissions({})
permissions.map((permission) => {
const { action, subject, fields, conditions } = permission
if (permission.inverted) {
return cannot(action, subject, fields, conditions) // Argument of type 'string' is not assignable to parameter of type 'SubjectTypeOf<AppAbility> | SubjectTypeOf<AppAbility>[]
}
return can(action, subject, subject, conditions)
})
return build()
}
there are still a lot to do like guard and request context though. So I am wondering if there would be a minimal example. Thank you in advance.