简体   繁体   中英

Problem implementing password reset functionality (stopping users from using old passwords) - MongoDB & NodeJS

I am implementing auth for a web app and want to implement forgot/reset password functionality, and the user is not supposed to enter old used passwords when resetting their password. My specific question is this:

I have 1 mongoose user model that looks like this:

const UserSchema = new mongoose.Schema({
  email: {
    type: String,
    required: true,
    unique: true,
  },
  password: {
    type: String,
    required: true,
    select: false,
  },
  passwordChangedAt: {
    type: Date,
    required: false,
    default: null,
  },
  role: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Role',
    required: true,
  },
  ...Base,
});

I also have one password reset model:

const PasswordResetSchema = new mongoose.Schema({
  user: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: true,
  },
  token: {
    type: String,
    required: true,
  },
  isUsed: {
    type: Boolean,
    required: false,
    default: false,
  },
  expireDate: {
    type: Date,
    required: true,
  },
  oldPassword: {
    type: String,
    required: true,
  },
  newPassword: {
    type: String,
    required: false,
    default: null,
  },
  ...Base,
});

Now when resetting the password I want to lookup in the password reset tables, and check if oldPassword or newPassword was already used before, and if it was used show an error to the user that they already once used that password.I use bcrypt to hash the password using a salt, which means that the same password can have a different hash in the database. So the problem is when querying the password reset collection, for same passwords use before, is there a way to query the passwords while comparing hashes and new plaintext password to check if the password was used.

  const hashedPassword = await bcrypt.hash(password, bcrypt.genSaltSync());
  // Here the hashedPassword is different, since the salt is different

  const usedSamePassPreviously = await PasswordReset.find({
    _id: { $nin: [passwordReset._id] },
    user: user._id,
    $or: [{ oldPassword: hashedPassword }, { newPassword: hashedPassword }],
    isUsed: true,
  });

I also tried querying using the $where clause in mongo, which allows me to use pure JavaScript function, but there we dont have access to bcrypt.compare function. I dont want to load all data in back-end and then find if a password was used, or use the same salt for all passwords since that is a security problem. How can i approach this query and is it possible to achieve this in the database side. Thanks in advance.

You'll need to iterate over the hashes of all your forbidden passwords and use bcrypt's .compare() method on each one. That way you can see whether the new password value presented to you by your user should be rejected as a duplicate.

Do this in your client code.

It's true that doing .compare() takes time; that's a design feature. That's a reason you should not do it in your database server: the database is a shared resource and you don't want to do cpu-intensive long running operations there; they'll take CPU needed to serve other database client code. Do those operations in your nodejs code. If the performance is really a problem, consider using a worker thread.

And, consider the information security implication: To do this in your database you must send it the secret plaintext password presented to you by your user. You should avoid sending secrets anywhere they're not absolutely needed. Database software is large and complex, and presents a large attack surface to cybercreeps. So sending it secrets it doesn't need makes those secrets just a little more vulnerable. That's true even if you don't actually store the secrets.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM