在mongoose中使用预保存挂钩不保存散列密码

时间:2017-03-26 21:58:21

标签: node.js mongodb

我正在使用Postman测试我正在为项目工作的API。我发送

{
    "fullName": "Smellydog",
    "emailAddress": "SmellydogCoding@gmail.com",
    "password": "password",
    "confirmPassword": "password"
}

作为此API路径的请求主体

users.post('/', (req, res, next) => {
  let user = new Users(req.body);
  user.save((error,user) => {
    if (error) {
      return next(error);
    } else {
      res.status = 201;
      res.location('/');
      res.end();
    }
  });
});

这是我的用户架构

const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

const UserSchema = new mongoose.Schema({
    fullName: {
      type: String,
      required: true,
      trim: true,
    },
    emailAddress: {
      type: String,
      required: true,
      unique: true,
      match: [emailRegex, "Please enter a valid email address"],
      trim: true
    },
    password: {
      type: String,
      required: true
    },
    confirmPassword: {
      type: String,
      required: true
    }
});

// hash password before saving to database
UserSchema.pre('save', function(next)  {
  bcrypt.hash(this.password, 10, function(error, hash) {
    if (error) {
      return next(error);
    } else {
      this.password = hash;
      this.confirmPassword = hash;
      next();
    }
  });
});

UserSchema.pre('validate', function(next) {
  let user = this;
  if (user.password !== user.confirmPassword) {
    return next('Passwords must match');
  } else {
    next();
  }
});

const User = mongoose.model('User', UserSchema);
module.exports = User;

预保存挂钩应该加密密码,然后将其保存到密码和confirmPassword字段。如果我使用Chrome调试器在预保存挂钩的末尾设置断点(其中调用next()),并检查this.password和this.confirmPassword,我看到它们被设置为新创建的散列,但是当我检查数据库之后,这两个字段都设置为原始密码字符串

{
    "_id": "58d835f0d026194610578c74",
    "fullName": "Smellydog",
    "emailAddress": "SmellydogCoding@gmail.com",
    "password": "password",
    "confirmPassword": "password",
    "__v": 0
  }

4 个答案:

答案 0 :(得分:2)

我同意odoreDogCoding先前的回答。该解决方案起作用的原因是因为它的上下文。当执行线程上线时: let user =this

“ this”的上下文是指正在创建的文档的实例。但是,一旦输入bcrypt hash方法的执行上下文,“ this”上下文将不再引用我们正在数据库中创建的文档的实例,而是引用函数的范围。

通过将对我们正在创建的文档对象的实例的引用保存在变量user中,我们保留了对要在散列后更新的this的引用。而且由于我们只是在进行浅表复制(实际上是对原始this的引用),所以当我们更新用户对象上的属性“ password”时,我们也在更新“ this”文档对象上的属性“ password”

答案 1 :(得分:0)

我添加了

let user = this;

到预保存挂钩的第一行,然后将字段称为

user.password
user.confirmPassword

喜欢这样

// hash password before saving to database
UserSchema.pre('save', function(next)  {
  let user = this;
  bcrypt.hash(user.password, 10, function(error, hash) {
    if (error) {
      return next(error);
    } else {
      user.password = hash;
      user.confirmPassword = hash;
      next();
    }
  });
});

似乎this.password正在预保存挂钩内部进行更新,但最后没有持久保存到数据库。将此设置为变量似乎改变了Paolo建议的上下文。我真的很想知道如何以及为什么这样做。

答案 2 :(得分:0)

这是因为您正在使用

bcrypt.hash(this.password, 10, function(error, hash)

.hash方法是异步的,这意味着您在散列密码之前正在运行next()函数。

改为使用异步/等待

UserSchema.pre('save', async function (next) {
  // check if password is present and is modified.
  try {
    if (this.password && this.isModified('password')) {
      this.password = await bcrypt.hash(this.password, passwordSaltRound);
    }
    next();
  } catch (err) {
    next(err);
  }
});

使用您的方法,您可以使用bcrypt.hashSync(this.password, 10, function(error, hash),但它将创建阻止代码!

所以只需使用async / await方法<3

答案 3 :(得分:0)

我知道这是一个老问题,但我想说另一个解决方案是使用 ES6 中引入的箭头函数表达式。

UserSchema.pre('save', function(next)  {
  bcrypt.hash(this.password, 10, (error, hash) => {
    if (error) {
      return next(error);
    } else {
      this.password = hash;
      this.confirmPassword = hash;
      next();
    }
  });
});

这样做的原因是因为箭头函数表达式没有自己的 this 绑定,所以它从定义函数的地方获取上下文;在这种情况下,这是用户对象的上下文。

您可以在 MDN Web Docs 上阅读更多关于箭头功能的信息here