casl icon indicating copy to clipboard operation
casl copied to clipboard

$exists operator does not work on mongoose document

Open danielbayerlein opened this issue 1 year ago • 3 comments

Describe the bug

{ $exists: boolean } operator does not work on mongoose document. With the same ability and a plain object it works fine.

To Reproduce

package.json

{
  "name": "example",
  "main": "index.js",
  "scripts": {
    "start": "node src/index.js"
  },
  "dependencies": {
    "@casl/ability": "^6.1.1",
    "mongoose": "^6.6.0"
  }
}

src/Post.js

const mongoose = require("mongoose");

const Post = new mongoose.Schema({
  title: String,
  author: String,
});

module.exports = mongoose.model("Post", Post);

src/defineAbility.js

const { defineAbility } = require("@casl/ability");

module.exports = defineAbility((can, cannot) => {
  can("manage", "Post", {
    author: { $exists: true },
  });
});

src/index.js

const ability = require("./defineAbility");
const subject = require("@casl/ability").subject;
const PostModel = require("./models/Post.js");

const draftPost = new PostModel({ title: "Lorem Draft" });
const myPost = new PostModel({
  title: "Lorem Ipsum",
  author: "Max Mustermann",
});

console.log("With mongoose");
console.log("can read draftPost", ability.can("read", draftPost)); // expected: false -> result: false
console.log("can read myPost", ability.can("read", myPost)); // expected: true -> result: false

console.log("With plain object");
console.log(
  "can read draftPost",
  ability.can("read", subject("Post", draftPost.toObject())) // expected: false -> result: false
);
console.log(
  "can read myPost",
  ability.can("read", subject("Post", myPost.toObject())) // expected: true -> result: true
);

Output

With mongoose
can read draftPost false
can read myPost false

With plain object
can read draftPost false
can read myPost true

Expected behavior

Supports $exists operator on mongoose documents.

CASL Version

@casl/ability - v6.1.1

Environment:

Node.js - v16.17.0

danielbayerlein avatar Sep 12 '22 14:09 danielbayerlein

The issue is that mongoose defines all properties from schema regardless exist in the doc or not. That’s why exist doesn’t work on it

stalniy avatar Sep 12 '22 15:09 stalniy

Thank you for the fast response ❤️ Should one therefore always use toObject() for mongoose documents?

danielbayerlein avatar Sep 12 '22 15:09 danielbayerlein

If I understand @stalniy correctly and all properties always exist, shouldn't the example above always return true because the check is { $exists: true }?

marco-streng avatar Sep 13 '22 06:09 marco-streng

OK, by looking into mongoose I see that it defines properties on model prototype. So,

console.log(myPost.hasOwnProperty('author')) // false
console.log(myPost.constructor.prototype.hasOwnProperty('author')) // true

And this is how $exists checks the existence -> https://github.com/stalniy/ucast/blob/master/packages/js/src/interpreters.ts#L70 . It requires property to be defined on the object itself.

Looking at sift.js it also uses hasOwnProperty but mingo.js gets the actual property.

Based on Mongo docs:

When is true, $exists matches the documents that contain the field, including documents where the field value is null. If is false, the query returns only the documents that do not contain the field

So, it's hard to judge whether document contain the field should be true if the field is actually in its prototype.

In your case, I think better to check based on empty string or write your own $exists that will work according to your expectations (i.e., checks whether property in object or its prototype chain). Check this guide for further details -> https://casl.js.org/v6/en/advanced/customize-ability

stalniy avatar Sep 18 '22 13:09 stalniy

that's why $exists for mongoose documents always returns false

stalniy avatar Sep 18 '22 13:09 stalniy

additionally, you can define default value for author to be null and check permissions based on `null.

stalniy avatar Sep 18 '22 13:09 stalniy

I do not plan change the behavior of $exists operator at least while the ask request for this feature is low. That's why close this issue.

stalniy avatar Sep 18 '22 13:09 stalniy