Reputation: 585
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