Как защитить поле пароля в 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.

Aaronheckmann
источник
это тоже сработает .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(...);
JohnnyHK
источник
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

Blogs.findOne({ _id: id }, { "password": 0 }).populate("user").exec()

Рики
источник
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;
  }
});
Netishix
источник
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функцию.

Нериус Джок
источник
2
router.get('/users',auth,(req,res)=>{
   User.findById(req.user.id)
    //skip password
    .select('-password')
    .then(user => {
        res.json(user)
    })
})
Десаи Рамеш
источник
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);
    });
});
Беннетт Адамс
источник
0

const userSchema = new mongoose.Schema(
  {
    email: {
      type: String,
      required: true,
    },
    password: {
      type: String,
      required: true,
    },
  },
  {
    toJSON: {
      transform(doc, ret) {
        delete ret.password;
        delete ret.__v;
      },
    },
  }
);

Рафал Бохневич
источник