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
});
}
}