VIVID
VIVID

Reputation: 585

CASL condition matcher is not matching

I have the following CASL ability factory:

type Subjects =
  | InferSubjects<
      | typeof Post
      | typeof User
    >
  | 'all';

export type AppAbility = PureAbility<[Action, Subjects]>;

@Injectable()
export class CaslAbilityFactory {
  createForUser(user: Partial<User>) {
    const { can, build } = new AbilityBuilder<PureAbility<[Action, Subjects]>>(
      PureAbility as AbilityClass<AppAbility>,
    );

    if (!user.emailVerified) {
      // Basic permissions for unverified users
      can(Action.Read, 'all');
      can([Action.Update, Action.Delete], User, { id: user.id });
    } else {
      // Verified user permissions
      can([Action.Create], Post);
      can([Action.Update, Action.Delete], Post, { authorId: user.id });
    }

    if (user.role === UserRole.MODERATOR) {
      // Additional permissions for moderators on top of verified user permissions
      can([Action.Update], Post);
    }

    if (user.role === UserRole.ADMIN) {
      // Admins can do anything, overriding all previous permissions
      can(Action.Manage, 'all');
    }

    return build({
      detectSubjectType: (item) =>
        item.constructor as ExtractSubjectType<Subjects>,
      });
  }
}

However, while using only basic conditions { authorId: user.id } (which I thought didn't need a custom conditionMatcher), it throws

You need to pass "conditionsMatcher" option in order to restrict access by conditions

I partially fixed by passing conditionsMatcher: buildMongoQueryMatcher({}) into the build() function argument object as a property. Although the error is gone, now the condition is not being checked i.e. user can edit other users' posts.

Why could it happen?


I found a related thread (with no answers): Nest.js + Casl conditional checking is not working

In the comments, it's mentioned that the OP ended up using nest-casl instead. I hope there's a way without switching to another package.


UPDATE: According to the comment, I tried to change PureAbility to Ability but seems like Ability does not exist in the latest CASL version. Below is my try to use createMongoAbility, but the result is still the same.

const conditionsMatcher = buildMongoQueryMatcher({ $ne, $nor, $eq }, { ne, nor, eq })

@Injectable()
export class CaslAbilityFactory {
  createForUser(user: Partial<User>) {
    const { can, cannot, build } = new AbilityBuilder<PureAbility<[Action, Subjects]>>(
      // PureAbility as AbilityClass<AppAbility>,
      createMongoAbility
    );
    console.log({ userId: user.id })
    if (user.role === UserRole.USER && !user.emailVerified) {
      // Basic permissions for unverified users
      can(Action.Read, 'all');
      can([Action.Update, Action.Delete], User, { id: user.id });
    } else if (user.role === UserRole.USER) {
      // Verified user permissions
      can([Action.Create], Post);
      can([Action.Update, Action.Delete], Post, { authorId: { $eq: user.id } });
    }

    if (user.role === UserRole.MODERATOR) {
      // Additional permissions for moderators on top of verified user permissions
      can([Action.Update], Post);
      can([Action.Create], ActionVote);
}

    if (user.role === UserRole.ADMIN) {
      // Admins can do anything, overriding all previous permissions
      can(Action.Manage, 'all');
    }

    return build({
      // Read https://casl.js.org/v6/en/guide/subject-type-detection#use-classes-as-subject-types for details
      detectSubjectType: (item) =>
        item.constructor as ExtractSubjectType<Subjects>,
        conditionsMatcher
      });
  }
}

Upvotes: 2

Views: 817

Answers (0)

Related Questions