Как защитить поле пароля в Mongoose / MongoDB, чтобы оно не возвращалось в запросе при заполнении коллекций?

Предположим, у меня есть две коллекции / схемы. Один из них - это схема пользователей с полями имени пользователя и пароля, а затем у меня есть схема блогов, которая имеет ссылку на схему пользователей в поле автора. Если я использую Mongoose для чего-то вроде

Blogs.findOne({...}).populate("user").exec()

У меня будет документ блога и пользователь, но как мне запретить Mongoose / MongoDB возвращать поле пароля? Поле пароля хешируется, но не должно возвращаться.

Я знаю, что могу опустить поле пароля и вернуть остальные поля в простом запросе, но как мне это сделать с помощью populate. Кроме того, есть ли какой-нибудь элегантный способ сделать это?

Кроме того, в некоторых ситуациях мне нужно получить поле пароля, например, когда пользователь хочет войти в систему или изменить пароль.


person Luis Elizondo    schedule 23.08.2012    source источник
comment
вы также можете сделать .populate ('user': 1, 'password': 0)   -  person Sudhanshu Gaur    schedule 25.07.2015


Ответы (13)


.populate('user' , '-password')

http://mongoosejs.com/docs/populate.html

Ответ JohnnyHK с использованием параметров схемы - это, вероятно, путь сюда.

Также обратите внимание, что query.exclude() существует только в ветке 2.x.

person aaronheckmann    schedule 23.08.2012
comment
это также будет работать .populate ('user': 1, 'password': 0) - person Sudhanshu Gaur; 25.07.2015
comment
Я не понимал, почему добавление - работает, и мне было любопытно, поэтому я нашел достойное объяснение в документации: при использовании строкового синтаксиса префикс пути с - будет отмечать этот путь как исключенный. Если путь не имеет префикса -, он включается. Наконец, если путь имеет префикс +, он принудительно включает путь, что полезно для путей, исключенных на уровне схемы. Вот ссылка на полную версию mongoosejs.com/docs/api.html#query_Query-select - person Connor; 30.09.2020

Вы можете изменить поведение по умолчанию на уровне определения схемы, используя атрибут поля select:

password: { type: String, select: false }

Затем вы можете использовать его по мере необходимости в вызовах find и populate, выбрав поле как '+password'. Например:

Users.findOne({_id: id}).select('+password').exec(...);
person JohnnyHK    schedule 23.08.2012
comment
Большой. Не могли бы вы привести пример того, кого добавить в поиск? Предполагая, что у меня есть: Users.find ({id: _id}) куда мне добавить + пароль +? - person Luis Elizondo; 23.08.2012
comment
Нашел пример по предоставленной вами ссылке. mongoosejs.com/docs/api.html#schematype_SchemaType-select Спасибо - person Luis Elizondo; 23.08.2012
comment
Есть ли способ применить это к объекту, переданному в обратный вызов save ()? Таким образом, когда я сохраняю профиль пользователя, пароль не включается в параметр обратного вызова. - person Matt; 29.06.2014
comment
На мой взгляд, это, безусловно, лучший ответ. Добавьте это один раз, и он будет исключен. Намного лучше, чем добавлять параметр выбора или исключения в каждый запрос. - person AndyH; 18.08.2016
comment
Это должен быть окончательный ответ. Добавлен в схему, и не нужно забывать об исключении во время запросов. - person KhoPhi; 09.11.2019
comment
Это самое гибкое решение! - person Chunlong Li; 10.12.2020
comment
Этот подход отлично работает при использовании populate, но он не будет работать при использовании агрегата с $ lookup. - person BaDr Amer; 18.04.2021

Редактировать:

Попробовав оба подхода, я обнаружил, что подход «исключать всегда» не работает для меня по какой-то причине с использованием стратегии локального паспорта, действительно не знаю почему.

Итак, вот что я в итоге использовал:

Blogs.findOne({_id: id})
    .populate("user", "-password -someOtherField -AnotherField")
    .populate("comments.items.user")
    .exec(function(error, result) {
        if(error) handleError(error);
        callback(error, result);
    });

Нет ничего плохого в подходе exclude always, он просто не работал с паспортом по какой-то причине, мои тесты показали мне, что на самом деле пароль был исключен / включен, когда я хотел. Единственная проблема с подходом include always заключается в том, что мне в основном нужно проходить каждый вызов, который я делаю в базе данных, и исключать пароль, что требует много работы.


После пары замечательных ответов я обнаружил, что есть два способа сделать это: «всегда включать и иногда исключать» и «всегда исключать и иногда включать»?

Пример того и другого:

Пример "всегда включать, но иногда исключать":

Users.find().select("-password")

or

Users.find().exclude("password")

Исключить всегда, но иногда включать:

Users.find().select("+password")

но вы должны определить в схеме:

password: { type: String, select: false }
person Luis Elizondo    schedule 23.08.2012
comment
Я бы выбрал последний вариант. Никогда не выбирайте пароль, кроме функций входа в систему / обновления пароля, в которых он вам действительно нужен. - person rdrey; 24.08.2012
comment
По какой-то причине этот вариант не работал с локальной стратегией Passport.js, не знаю почему. - person Luis Elizondo; 24.08.2012
comment
Хороший ответ, спасибо !!! Не знаю почему, но когда я делаю .select("+field"), он приносит только __id, хотя .select("-field") красиво исключает поле, которое я хочу - person Renato Gama; 13.02.2013
comment
Извините, работает отлично, не заметил, что select: false является обязательным - person Renato Gama; 13.02.2013
comment
Это работает для моей локальной стратегии: await User.findOne ({email: username}, {password: 1}, async (err, user) = ›{...}); - person TomoMiha; 13.03.2019

Вы можете добиться этого, используя схему, например:

const UserSchema = new Schema({/* */})

UserSchema.set('toJSON', {
    transform: function(doc, ret, opt) {
        delete ret['password']
        return ret
    }
})

const User = mongoose.model('User', UserSchema)
User.findOne() // This should return an object excluding the password field
person Ikbel    schedule 27.05.2018
comment
Я проверил каждый ответ, я думаю, что это лучший вариант, если вы разрабатываете api. - person asmmahmud; 10.08.2018
comment
Это не удаляет поля по населению. - person JulianSoto; 19.08.2019
comment
Лучший вариант для меня, этот подход не конфликтует с моим методом аутентификации. - person Mathiasfc; 04.10.2019
comment
Это лучший способ, если вы используете API. Вам никогда не придется беспокоиться о том, что вы забудете удалить поле :) - person Sam Munroe; 17.11.2020

User.find().select('-password') - правильный ответ. Вы не можете добавить select: false в схему, так как он не будет работать, если вы захотите войти в систему.

person Ylli Gashi    schedule 13.04.2017
comment
Не можете ли вы переопределить поведение в конечной точке входа в систему? Если да, то это самый безопасный вариант. - person erfling; 06.06.2018
comment
@erfling, не будет. - person jrran90; 22.04.2019
comment
Это будет, вы можете использовать const query = model.findOne({ username }).select("+password"); и использовать его для маршрутов смены / сброса логина и пароля и в противном случае гарантировать, что он никогда не появится. Это намного безопаснее, чтобы он не возвращался по умолчанию, так как люди, как доказано, ошибаются. - person Cacoon; 12.05.2020

Я использую для скрытия поля пароля в моем ответе REST JSON

UserSchema.methods.toJSON = function() {
 var obj = this.toObject(); //or var obj = this;
 delete obj.password;
 return obj;
}

module.exports = mongoose.model('User', UserSchema);
person Gere    schedule 05.12.2018

Предполагая, что ваше поле пароля - «пароль», вы можете просто сделать:

.exclude('password')

Более подробный пример можно найти здесь

Это сосредоточено на комментариях, но в игре действует тот же принцип.

Это то же самое, что использовать проекцию в запросе в MongoDB и передать {"password" : 0} в поле проекции. См. здесь

person Adam Comerford    schedule 23.08.2012
comment
Большой. Спасибо. Мне нравится такой подход. - person Luis Elizondo; 23.08.2012

Я нашел другой способ сделать это, добавив некоторые настройки в конфигурацию схемы.

const userSchema = new Schema({
    name: {type: String, required: false, minlength: 5},
    email: {type: String, required: true, minlength: 5},
    phone: String,
    password: String,
    password_reset: String,
}, { toJSON: { 
              virtuals: true,
              transform: function (doc, ret) {
                delete ret._id;
                delete ret.password;
                delete ret.password_reset;
                return ret;
              }

            }, timestamps: true });

Добавив функцию преобразования к объекту toJSON с именем поля, которое нужно исключить. как указано в документах:

Нам может потребоваться выполнить преобразование полученного объекта на основе некоторых критериев, например, чтобы удалить некоторую конфиденциальную информацию или вернуть настраиваемый объект. В этом случае мы устанавливаем необязательную функцию transform.

person Nerius Jok    schedule 30.09.2019

При использовании password: { type: String, select: false } вы должны помнить, что он также исключает пароль, когда он нам нужен для аутентификации. Так что будьте готовы справиться с этим, как хотите.

person Anand Kapdi    schedule 22.07.2018

Решение - никогда не хранить пароли в виде открытого текста. Вам следует использовать такой пакет, как bcrypt или пароль-хеш.

Пример использования для хеширования пароля:

 var passwordHash = require('password-hash');

    var hashedPassword = passwordHash.generate('password123');

    console.log(hashedPassword); // sha1$3I7HRwy7$cbfdac6008f9cab4083784cbd1874f76618d2a97

Пример использования для проверки пароля:

var passwordHash = require('./lib/password-hash');

var hashedPassword = 'sha1$3I7HRwy7$cbfdac6008f9cab4083784cbd1874f76618d2a97';

console.log(passwordHash.verify('password123', hashedPassword)); // true
console.log(passwordHash.verify('Password0', hashedPassword)); // false
person Cameron Hudson    schedule 30.06.2018
comment
Независимо от того, хешируется пароль или нет, пароль / хеш никогда не должны показываться пользователю. Злоумышленник может получить важную информацию о хешированном пароле (какой алгоритм был использован) и т.д. - person Marco; 16.01.2019

Вы можете передать объект DocumentToObjectOptions в schema.toJSON () или schema.toObject ().

См. Определение TypeScript из @ types / mongoose

 /**
 * The return value of this method is used in calls to JSON.stringify(doc).
 * This method accepts the same options as Document#toObject. To apply the
 * options to every document of your schema by default, set your schemas
 * toJSON option to the same argument.
 */
toJSON(options?: DocumentToObjectOptions): any;

/**
 * Converts this document into a plain javascript object, ready for storage in MongoDB.
 * Buffers are converted to instances of mongodb.Binary for proper storage.
 */
toObject(options?: DocumentToObjectOptions): any;

DocumentToObjectOptions имеет параметр преобразования, который запускает настраиваемую функцию после преобразования документа в объект javascript. Здесь вы можете скрыть или изменить свойства в соответствии с вашими потребностями.

Итак, допустим, вы используете schema.toObject () и хотите скрыть путь к паролю из своей пользовательской схемы. Вам следует настроить общую функцию преобразования, которая будет выполняться после каждого вызова toObject ().

UserSchema.set('toObject', {
  transform: (doc, ret, opt) => {
   delete ret.password;
   return ret;
  }
});
person netishix    schedule 11.04.2019

Это скорее следствие исходного вопроса, но это был вопрос, с которым я столкнулся, пытаясь решить свою проблему ...

А именно, как отправить пользователя обратно клиенту в обратном вызове user.save () без поля пароля.

Пример использования: пользователь приложения обновляет информацию / настройки своего профиля из клиента (пароль, контактная информация, whatevs). Вы хотите отправить обновленную информацию о пользователе обратно клиенту в ответе после успешного сохранения в mongoDB.

User.findById(userId, function (err, user) {
    // err handling

    user.propToUpdate = updateValue;

    user.save(function(err) {
         // err handling

         /**
          * convert the user document to a JavaScript object with the 
          * mongoose Document's toObject() method,
          * then create a new object without the password property...
          * easiest way is lodash's _.omit function if you're using lodash 
          */

         var sanitizedUser = _.omit(user.toObject(), 'password');
         return res.status(201).send(sanitizedUser);
    });
});
person Bennett Adams    schedule 26.07.2016

person    schedule
comment
Не могли бы вы объяснить, почему это решает проблему? - person AliAvci; 25.02.2021