casl: Rule with class and conditions doesn't work

First at all, sorry if I forgot something.

Describe the bug When I define rules using a class name in typescript, in that rules where there are some condition always return false.

To Reproduce

I have these classes User and Post

enum Role {
  ADMIN = 'admin',
  USER = 'user'
}

class User {
  id: number;
  login: string;
  role: Role;
}

class Post {
  title: string;
  text: string;
  authrorId: number;
}

I define the rules in a static class method called createForUser as follow:

enum Action {
  MANAGE = 'manage',
  CREATE = 'create',
  READ = 'read',
  UPDATE = 'update',
  DELETE = 'delete'
}

type Subjects = typeof Post | typeof User | Post | User | 'all';
type AppAbility = Ability<[Action, Subjects}>;

class CaslAbilityFactory {
  static createForUser(user: User) {
    cosnt { can, cannot, build } = new AbilityBuilder<AppAbility>(Ability as AbilityClass<AppAbility>);
    if (user.role === Role.ADMIN) {
      can(Action.MANAGE, 'all');
    } else {
      can(Action.READ, Post);
      cannot(Action.DELETE, Post);
    }
    can(Action.UPDATE, Post, { authorId: user.id });
    return build();
  }
}

When I check the ability of READ a post, with a user with Role USER, it works as expected. The problem is when I check to update a post as follow:

const user: User = new User();
user.id = 1;
user.login = 'user01';
user.role = Role.USER;

const post: Post = new Post();
post.title = "Dummy post";
post.text = "Something interesting";
post.authorId = user.id;

const ability = CaslAbilityFactory.createForUser(user);
console.log(ability.can(Action.READ, Post));  // ---> Prints true and expected true.
console.log(ability.can(Action.UPDATE, post));  // ---> Prints false, but expected true

But if we add 'Post' to the type Subjects and change Post for 'Post' as following:

type Subjects = typeof Post | typeof User | Post | User | 'Post' | 'all';

class CaslAbilityFactory {
  ...
  
  can(Action.UPDATE, 'Post', { authorId: user.id });
  ...
  return build();
}

console.log(ability.can(Action.READ, Post));  // ---> Prints true and expected true.
console.log(ability.can(Action.UPDATE, post));  // ---> Now prints true and expected true

I tried too of downgrade @casl/ability to version 4.1.6 and the first code (using Post in the rule specification) works as expected.

Interactive example Here is a link to the interactive example in codesandobx. https://codesandbox.io/s/casl-bug-3txfo?file=/src/index.ts

CASL Version @casl/ability - 5.1.1

Environment: node - 12.18.4 typescript - 4.1.3

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 21 (9 by maintainers)

Most upvoted comments

Hello there! Have similar problem discussed above. Can’t find a solution. Kindly ask you to help with it. Here is a link to the description: https://stackoverflow.com/questions/70584146/nest-js-authorization-with-casl-doesnt-work-as-expected

@ThomasEddie you didn’t specify detectSubjectType function:


static createForUser(user: User) {
  // all code except return
  return build({
    detectSubjecType: type => type.constructor as any
  })
}

@ThomasEddie You need to send a instance of BlogEntity. What you have is like you were doing ability.cannot(Action.Delete, BlogEntity), but you need do ability.cannot(Action.Delete, blogEntityInstance) for the cannot(Action.Delete, BlogEntity, { isPublished: true }); rule to be executed. You will need to make some changes in your guard to send to the handler that instance. I hope I have explained myself well.

fixed in 5.1.2

To be able to do it (without typescript errors) I had to add // @ts-ignore because detectSubjectType expects a string, but type.constructor returns a Function

static createForUser(user: User) {
  // all code except return
  return build({
    // @ts-ignore
    detectSubjecType: type => type!.constructor
  })
}

That is! When there is no “instance conditions” (don’t know how name it exactly 😅) it works fine.