Skip to content

moh1434/casl-prisma-generator

Repository files navigation

casl-prisma-generator

casl-prisma-generator generates CASL subjects from schema.prisma file

Installation

  npm install casl-prisma-generator
 # or
  yarn add casl-prisma-generator
 # or
  pnpm add casl-prisma-generator

Usage Example

In package.json add:

"casl-generate": "npx ts-node ./src/casl/generator.ts",
"prisma:generate": "prisma generate && npm run casl-generate",

Asuming we have this schema.prisma file:

enum UserType {
  ADMIN
  USER
}

model User {
    id        String   @id @default(uuid())
    type      UserType @default(USER)
    password  String
}

model Post {
    id      String @id @default(uuid())
    content String @db.VarChar(2048)
}

model Item {
    id String @id @default(uuid())
}

model Subject {
    id String @id @default(uuid())
}

Example1 src/casl/generator.ts

import { generateCaslSubjectsToFile } from 'casl-prisma-generator/dist';

generateCaslSubjectsToFile('src/casl/generated/subjectsList.ts');

The result will be: src/casl/generated/subjectsList.ts:

/** this file is auto generated, don't touch it **/

import { User, Post, Item, Subject } from '@prisma/client';

export type SubjectsList = {
  User: User;
  Post: Post;
  Item: Item;
  Subject: Subject;
};

Example2 src/casl/generator.ts

import {
  generateCaslSubjectsToFile,
  OverrideSubjects,
} from 'casl-prisma-generator/dist';

const overrides: OverrideSubjects = {
  User: {
    typeName: 'JwtUser',
    importPath: "import { JwtUser } from 'src/auth/types-auth';",
  },
  Post: null,
};
generateCaslSubjectsToFile('src/casl/generated/subjectsList.ts', overrides, {
  prismaSchemaPath: 'prisma/schema.prisma',
  prismaClientPath: '@prisma/client',
});

The result will be: src/casl/generated/subjectsList.ts:

/** this file is auto generated, don't touch it **/

import { Item, Subject } from '@prisma/client';
import { JwtUser } from 'src/auth/types-auth';

export type SubjectsList = {
  User: JwtUser;
  Item: Item;
  Subject: Subject;
};

Notice: User: JwtUser; and we removed Post: Post;

Then in our CASL setup: src/casl/casl-ability.factory.ts

import { AbilityBuilder, PureAbility } from '@casl/ability';
import { Subjects } from '@casl/prisma';
import { PrismaQuery, createPrismaAbility } from '@casl/prisma';
import { JwtUser } from 'src/auth/types-auth';

import { SubjectsList } from 'src/casl/generated/subjectsList';

export type Action = 'manage' | 'create' | 'read' | 'update' | 'delete';

export type AppAbility = PureAbility<
  [Action, Subjects<SubjectsList> | 'all'],
  PrismaQuery
>;

export function createForUser(user: JwtUser) {
  const { can, cannot, build } = new AbilityBuilder<AppAbility>(
    createPrismaAbility,
  );

  if (user.type === 'ADMIN') {
    can('manage', 'all'); // read-write access to everything
  } else {
    can('manage', 'User', { id: user.id });
  }

  return build();
}

Working Project:

nest-prisma-casl-starter: this project implements CASL and Prisma with NestJs