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


86

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

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

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

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

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


вы также можете сделать .populate ('user': 1, 'password': 0)
— Sudhanshu Gaur

Ответы:


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

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

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

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


это тоже сработает .populate ('user': 1, 'password': 0)
— Sudhanshu Gaur

Я не понимал, почему добавление «-» работает, и мне было любопытно, поэтому я нашел достойное объяснение в документации: при использовании строкового синтаксиса префикс пути с помощью - будет отмечать этот путь как исключенный. Если путь не имеет префикса -, он включается. Наконец, если путь имеет префикс +, он принудительно включает путь, что полезно для путей, исключенных на уровне схемы. Вот ссылка на полную информацию mongoosejs.com/docs/api.html#query_Query-select
— Коннор,

305

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

password: { type: String, select: false }

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

Users.findOne({_id: id}).select('+password').exec(...);

3
Отлично. Не могли бы вы привести пример того, кого добавить в поиск? Предполагая, что у меня есть: Users.find ({id: _id}), куда мне добавить "+ пароль +?"
— Луис Элизондо,

Нашел пример по предоставленной вами ссылке. mongoosejs.com/docs/api.html#schematype_SchemaType-select Спасибо
— Луис Элизондо

9
Есть ли способ применить это к объекту, переданному в обратный вызов save ()? Таким образом, когда я сохраняю профиль пользователя, пароль не включается в параметр обратного вызова.
— Мэтт

2
На мой взгляд, это, безусловно, лучший ответ. Добавьте это один раз, и он будет исключен. Намного лучше, чем добавлять параметр выбора или исключения в каждый запрос.
— AndyH

Это должен быть окончательный ответ. Добавлен в схему, и не нужно забывать об исключении во время запросов.
— KhoPhi

23

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

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

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

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")

или же

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

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

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

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

password: { type: String, select: false }

Я бы выбрал последний вариант. Никогда не выбирайте пароль, кроме функций входа в систему / обновления пароля, в которых он вам действительно нужен.
— rdrey

По какой-то причине этот вариант не работал с локальной стратегией Passport.js, не знаю почему.
— Луис Элизондо,

Хороший ответ, спасибо !!! Не знаю почему, но когда я это делаю, .select("+field")это приносит только то поле, которое я хочу __id, хотя и .select("-field")хорошо исключает,
— Ренато Гама

Извините, работает отлично, не заметил, что select: falseэто обязательно
— Ренато Гама

1
Это работает для моей локальной стратегии: await User.findOne ({email: username}, {password: 1}, async (err, user) => {...});
— TomoMiha

10

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


Вы не можете переопределить поведение в конечной точке входа в систему? Если да, то это самый безопасный вариант.
— erfling

@erfling, не будет.
— jrran90

1
Это будет, вы можете использовать const query = model.findOne({ username }).select("+password");и использовать это на маршрутах смены / сброса логина и пароля и в противном случае гарантировать, что он никогда не появится. Это намного безопаснее, чтобы он не возвращался по умолчанию, поскольку люди, как доказано, совершают ошибки, imo
— Cacoon

10

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

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

3
Я проверил все ответы, я думаю, что это лучший вариант, если вы разрабатываете api.
— asmmahmud

Это не удаляет поля по населению.
— JulianSoto

1
Наилучший вариант для меня, этот подход не противоречит моему методу аутентификации
— Mathiasfc

Это лучший способ, если вы используете API. Вам никогда не придется беспокоиться о том, что вы забудете удалить поле :)
— Сэм Манро

4

Я использую для скрытия поля пароля в моем ответе 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);

3

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

.exclude('password')

Существует более обширный пример здесь

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

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


Отлично. Благодарю. Мне нравится такой подход.
— Луис Элизондо


2

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

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

 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

8
Независимо от того, хешируется пароль или нет, пароль / хеш никогда не должны показываться пользователю. Злоумышленник может получить важную информацию о хешированном пароле (какой алгоритм использовался) и т.д.
— Марко

2

Вы можете передать объект 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;
  }
});

2

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

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функцию.



1

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


0

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

А именно, как отправить пользователя обратно клиенту в обратном вызове 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);
    });
});

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.