Chenya Zhang Chenya Zhang - 3 months ago 31
Node.js Question

Mongoose "schema method" callback not working

I'm new to mongoose and node.js. I try to follow this tutorial: https://scotch.io/tutorials/using-mongoosejs-in-node-js-and-mongodb-applications#sample-model-for-users

In my entry point index.js, I tried to call "chenya.saltHashPassword(function(err, passwordHash)". It is actually called in user.js because user.js can print out three log messages; however, there is no log message for this method call at all in index.js. In contrast, the save method can print out log message indicating successful saving.:

//Lets load the mongoose module in our program
var mongoose = require('mongoose');

//Lets connect to our database using the DB server URL.
mongoose.connect('mongodb://localhost:27017/server_naturis');

// if our user.js file is at app/models/user.js
var User = require('./user');

// create a new user called Chenya
var chenya = new User({
userName: 'Chenya',
email: 'chenya@gmail.com',
password: 'Chenya'
});

// call the custom method. hash the password
chenya.saltHashPassword(function(err, passwordHash) { // NOT CALLED!
if (err) {
console.log('chenya.saltHashPassword: ' + err);
} else {
this.password = passwordHash;
console.log('Your hashed password is ' + passwordHash);
}
});

// call the built-in save method to save to the database
chenya.save(function(err) { // CALLED!
if (err) {
console.log('chenya.save: ' + err);
} else {
console.log('User saved successfully!');
}
});


In my user.js, I have the schema function "userSchema.methods.saltHashPassword":

// grab the things we need
var mongoose = require('mongoose');
var Schema = mongoose.Schema;

// Require the crypto module for password hash
'use strict';
var crypto = require('crypto');

// create a schema
var userSchema = new Schema({
userName: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
});

// add a schema method
/**
* generates random string of characters i.e salt
* @function
* @param {number} length - Length of the random string.
*/
var genRandomString = function(length){
return crypto.randomBytes(Math.ceil(length/2))
.toString('hex') /** convert to hexadecimal format */
.slice(0,length); /** return required number of characters */
};
/**
* hash password with sha512.
* @function
* @param {string} password - List of required fields.
* @param {string} salt - Data to be validated.
*/
var sha512 = function(password, salt){
var hash = crypto.createHmac('sha512', salt); /** Hashing algorithm sha512 */
hash.update(password);
var value = hash.digest('hex');
return {
salt:salt,
passwordHash:value
};
};
/**
* a function that will use the above function
* to generate the hash that should be stored
* in the database as user’s password.
*/
userSchema.methods.saltHashPassword = function() {
var salt = genRandomString(16); /** Gives us salt of length 16 */
var passwordData = sha512(this.password, salt);
console.log('UserPassword = '+ this.password);
console.log('Passwordhash = '+ passwordData.passwordHash);
console.log('\nSalt = '+ passwordData.salt);
return passwordData.passwordHash;
}

// the schema is useless so far
// we need to create a model using it
var User = mongoose.model('User', userSchema);

// make this available to our users in our Node applications
module.exports = User;


Terminal:

UserPassword = Chenya
Passwordhash = 5bb5bf2181e2c713bae1eb49d1f3646b23db839368d38c33951774c92cec39a3c4b855aea30875e72cce6f271bdbdb27de8976c9316df09d086435b6c5629548

Salt = a88384d072b720de
(node:11717) DeprecationWarning: Mongoose: mpromise (mongoose's default promise library) is deprecated, plug in your own promise library instead: http://mongoosejs.com/docs/promises.html
User saved successfully!

Answer

You're not passing in a callback parameter into userSchema.methods.saltHashPassword but treating the function as if you did.

Change userSchema.methods.saltHashPassword to:

userSchema.methods.saltHashPassword = function(callback) { // <-- Add callback param
    var salt = genRandomString(16); /** Gives us salt of length 16 */
    var passwordData = sha512(this.password, salt);
    console.log('UserPassword = '+ this.password);
    console.log('Passwordhash = '+ passwordData.passwordHash);
    console.log('\nSalt = '+ passwordData.salt);

    // Your function that you passed in is called here
    callback(null, passwordData.passwordHash);
}

The reason that you're callback was not called in saltHashPassword but was called in save is because Mongoose defines that method to require a callback function that takes in a parameter for an error and the actual return value.

When an error occurs, it is expected that the callback will have error handling defined, which is good practice and why you see tutorials suggesting you do the same. When you define your own method for a Schema you no longer have that and have to set it up yourself.

So as you can see in the function above, that's what happened. Now you're callback is passed in as a parameter calling it with callback(null, passwordData.passwordHash) will make it execute. If you ever had an error occur, you could then save it as variable, e.g. err and pass that into your function as callback(err, null)

Going back to salts. I haven't read your tutorial but it's important that you save them in the database along with your user data so that passwords can be verified when a user logs in.

Good resource here:

Password Hashing add salt + pepper or is salt enough?

You need the salt to generate the same hashed password you store in the database. If you don't have access to that salt, you can't know if the password you're given would generate the same hash.

Comments