albertfdp
albertfdp

Reputation: 427

Check CASL ability before loading an entity

I have the following abilities defined in CaslAbilityFactory:

    if (auth.isAdmin) {
      can(Action.Create, User);
      can(Action.Read, "all");
      can(Action.Manage, User);
      can(Action.Update, User, ["email", "firstName", "lastName", "role"]);

      can(Action.Delete, User);
      cannot(Action.Delete, User, isOwner);
    }

    if (auth.isUser) {
      can([Action.Read, Action.Update], User, isOwner);
    }

Then, I have defined the following resolver:

  @Mutation(() => User)
  @UseGuards(PoliciesGuard)
  @CheckPolicies(ability => ability.can(Action.Update, User))
  async updateUser(
    @Args("input") input: UpdateUserInput,
    @CurrentAuth() auth,
  ): Promise<User> {
    const ability = this.caslAbilityFactory.createForAuth(auth);

    if (ability.cannot(Action.Update, { id: input.id }) {
      throw new ForbiddenException();
    }

    return this.usersService.update(input.id, input);
  }

The @CheckPolicies decorator correctly ensures that only a user with the Update action for users can call it, but I need to assert that for this specific one, can I update it? If the requester is the owner, it can. But how do you check that before loading the user?

An obvious solution is to load the user, run the check, then perform the update. Another more hacky solution would be to somehow cast it to new User({ id }) and run the check.

Is there a better solution for this?

Thanks,

Upvotes: 0

Views: 47

Answers (1)

Sergii Stotskyi
Sergii Stotskyi

Reputation: 5390

There are few options to do this. 2 of them you mentioned. Another one is to convert casl conditions to database query and let db check conditions.

This is possible for casl-prisma. There is also some work done in this direction for other SQL libs but it hasn’t been finished.

Casting POJO to User is the worst option because you reveals permissions to controller. You know that only owners can access this object that’s why you hardcode object shape so casl returns you expected result. BUT as soon as you change this permissions on casl level, you will also need to update your hardcoded POJO shape. So basically you loose flexibility which casl gives you - changing permissions in single place.

That’s why there are only 2 viable long term solutions:

  1. Load entity from db and check, if you have an id it will be fast
  2. Convert permissions to db conditions and let db check whether user can access an entity

Upvotes: 1

Related Questions