casl
casl copied to clipboard
$exists operator does not work on mongoose document
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
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
Thank you for the fast response ❤️ Should one therefore always use toObject()
for mongoose documents?
If I understand @stalniy correctly and all properties always exist, shouldn't the example above always return true
because the check is { $exists: true }
?
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
that's why $exists
for mongoose documents always returns false
additionally, you can define default value for author
to be null
and check permissions based on `null.
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.