For hashing a password before the object is saved to mongodb, i use the built-in pre-save hook comming with mongoose. But what is the correct way to handle hashing during updates?
I tried to solve this with the pre-update hook, but this has some significant disadvantages, as it bypasses the model validation (e.g password length).
That was my approach (shortened):
userSchema.pre('findOneAndUpdate', function (next) {
let query = this;
let update = query.getUpdate();
if (!update.password) {
return next();
}
hashPassword(update.password, function (err, hash) {
//handle error or go on...
//e.g update.password = hash;
});
});
So, what is the preferred way to solve this problem?
While MongoDB's default security is based on modern industry standards, such as TLS for the transport-layer and SCRAM-SHA-2356 for password exchange, it's still possible for someone to get into your database, either by attacking your server through a different vector, or by somehow obtaining your security credentials.
One good method to use is the update() method. It matches a document based on the filter specified, and then makes the update based on the update supplied as well. However, the update() function does not return the updated document but returns a write result.
In MongoDB, an upsert is an update query that inserts a new document if no document matches the given filter. To upsert a document in Mongoose, you need to set the upsert option to true in updateOne() method as shown below: const mongoose = require('mongoose') const User = mongoose.
I would use a pre 'save'
middleware:
AccountSchema.pre('save', function(next) {
this._doc.password = encrypt(this._doc.password);
next();
});
But doing this requires to use save
also for updating a document:
Account.findById(id, function(err, doc) {
if (err) return false;
doc.password = "baloony2";
doc.save();
});
As stated in the documentation for update:
[...]
Although values are casted to their appropriate types when using update, the following are not applied:
- defaults
- setters
- validators
- middleware
If you need those features, use the traditional approach of first retrieving the document.
A verifiable example:
const crypto = require('crypto');
const algorithm = 'aes-256-ctr';
const password = 'aSjlkvS89';
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
mongoose.connect("mongodb://localhost:33023/test_1");
mongoose.Promise = require('bluebird');
function encrypt(text) {
var cipher = crypto.createCipher(algorithm, password);
var crypted = cipher.update(text, 'utf8', 'hex');
crypted += cipher.final('hex');
return crypted;
}
// SCHEMA -------------------------------------------------
var AccountSchema = new Schema({
name: {
type: String,
unique: true
},
password: String
});
var id;
AccountSchema.pre('save', function(next) {
id = this._doc._id;
var pwd = this._doc.password;
console.log("hashing password: " + pwd);
this._doc.password = encrypt(pwd);
next();
});
// MODEL --------------------------------------------------
var Account = mongoose.model('Account', AccountSchema);
// DATA ---------------------------------------------------
new Account({
name: "john",
password: "baloony1"
})
.save()
.then(function(res) {
Account.findById(id, function(err, doc) {
if (err) return false;
doc.password = "baloony2";
doc.save();
});
});
Extra info on the example:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With