When using a plugin (I'm using mongoose-user) I've noticed that from methods defined in schema.methods inside a plugin (in my example mongoose-user.js) it is not possible to get the value of fields that have been marked as "select: false" in the model (in my example user.js)
So if you apply "select: false" to a field it won't be accesible from a plugin.
By removing this property or setting it to "select: true" the field is accessible again.
Updated info: I've also tested moving the plugin logic to user.js and the problem persists. I can't read values using this.field from user.js methods too.
Sample using a plugin: (see line console.log('mongoose-use hashed_password', this.hashed_password); )
// model/user.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var userPlugin = require('mongoose-user');
var UserSchema = new Schema({
name: { type: String, default: '' },
email: { type: String, default: '' },
hashed_password: { type: String, default: '', select: false },
});
UserSchema.plugin(userPlugin, {});
mongoose.model('User', UserSchema)
// mongoose-user.js
module.exports = userPlugin
function userPlugin (schema, options) {
var crypto = require('crypto');
schema.methods.authenticate = function(plainText) {
// THIS CONSOLE.LOG CAN'T ACCES TO this.hashed_password value
// IT ALWAYS RETURN 'undefined'.
console.log('mongoose-use hashed_password', this.hashed_password);
return this.encryptPassword(plainText) === this.hashed_password
}
schema.methods.encryptPassword = function (password) {
if (!password) return '';
return crypto.createHmac('sha1', this.salt).update(password).digest('hex');
}
}
This is working as intended. { select : false }
means the field will not be queried from the database at all. Thus, you cannot have access to it inside the method unless you specifically override that setting.
Ok thanks for the explanation.
I thought that select
only affected database selection because there is a select
command in driver which is related with queries.
I didn't expected having no access to the field from the model.
I just wanted to use the { select: false }
feature in order to avoid selecting 'sensitive' fields such as passwords or id's in every find command but also wanted to use them in the model when performing 'pre' actions.
I'm wondering if there is a way to do it.
Did you find a way to handle this issue? I'm having the same problem.
When you say "select: false", the data won't be loaded from the database at all, as EJ said. You can use a custom toObject()
transformation if you want to filter out fields before sending them over HTTP for instance. What specifically are you trying to achieve?
you can override the select false fields with select for your queries (SO thread here)
// add back the password field for this query
var query = User.findOne({
email: req.body.email
}).select('_id email +password');
query.exec(function(err, user){
// user with included password field
});
My user schema in this case looked like:
var userSchema = new Schema({
email: {type: String, required: true, unique: true },
password: {type: String, required: true, select: false },
admin: Boolean
});
@vkarpov15 Would it make sense to have access to a this.select()
method to selectively request the missing fields only when necessary?
For example:
ResourceSchema
.virtual('voted')
.get(function() {
if (global.user) {
this.select( 'voters');
return _.includes(this.voters, global.user._id);
}
return null;
});
Voters would hypothetically contain the ids of users who have voted (some resource). Let's say we don't want to select them by default, but want to check if the logged in user has already voted (set in global in the example).
I suppose it would be less efficient, needing another query to the database, but in some cases where performance isn't critical it might make sense to know that's automatically taken care of.
@kesarion would be more efficient and cleaner to apply that logic to filter out the voters
field after running the query if the user has not voted than to execute a separate query specifically for the voters
field.
An alternative to using select:false
is to add an instance method called clean()
to your models, and call it before sending data:
schema.methods.clean = function(){
const obj = this.toObject();
const sensitive = ['password','activation_token'];//these are the fields you want to remove
sensitive.forEach((item)=>{
delete obj[item];
});
return obj;
};
NOTE: if you are using virtuals and want them preserved, add toObject:{ virtuals: true }
to your schema options
@r3wt your approach works, but I'd argue against it because you rarely want to include a user's password in the user document as described in this blog post. Keeping the password hash in the user document creates more problems than it solves in my experience.
good general advice, but my way is fine if you are careful not to expose the hash in responses.
On May 10, 2019 4:31 PM, Valeri Karpov notifications@github.com wrote:
@r3wthttps://github.com/r3wt your approach works, but I'd argue against it because you rarely want to include a user's password in the user document as described in this blog posthttps://thecodebarbarian.com/thoughts-on-user-passwords-in-rest-apis. Keeping the password hash in the user document creates more problems than it solves in my experience.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHubhttps://github.com/Automattic/mongoose/issues/1596#issuecomment-491436086, or mute the threadhttps://github.com/notifications/unsubscribe-auth/ABCGR7NNOZCDMYQS2JNQ6A3PUXSRNANCNFSM4AG2LNBA.
Most helpful comment
you can override the select false fields with select for your queries (SO thread here)
My user schema in this case looked like: