Mike Warren
Mike Warren

Reputation: 3886

functions not being reached

I am trying to get login service prototype up, as first step in server-side part of this personal project I am working on. I attempt to reference the code here and here, as it is out of the Learning Node.js book (by Marc Wandschneider), which has been out for three years, and is thus already proven to work.

My actual server code can be found at this StackOverflow link. I skip the database part because I have trouble getting it up, and instead, I make my ./helpers/users.js look like:

exports.version = "0.0.1";

var async = require('async'),
    bcrypt = require('bcrypt');

var helpers = require('./helpers.js');

function User(id, email, displayName, password, deleted)
{
    this.userID = id;
    this.email = email;
    this.displayName = displayName;
    if (User.connectedToDatabase) this._password = password;
    else
    {
        bcrypt.genSalt(10, function (err, salt) {
            // this, for some reason, isn't getting called. Literally, I never see "I'm here"
            console.log("I'm here...");
            bcrypt.hash(password, salt, function (err, hash) { 
                if (!err)
                {
                    this._password = hash;
                    console.log("this._password == " + this._password);
                }
                else
                {
                    console.log("Error occurred: ");
                    console.log(err);
                }
            })
        });
    }
    //this._password = password;
    this.deleted = deleted;
}

User.connectedToDatabase = false;

User.prototype.userID = 0;
User.prototype.email = null;
User.prototype.displayName = null;
User.prototype._password = null;
User.prototype.deleted = false;

User.prototype.checkPassword = function (password, callback)
{
    bcrypt.compare(password, this._password, callback); // returns false
}
User.prototype.responseObject = function() {
    return {
        id: this.userID,
        email: this.email,
        displayName: this.displayName
    };
}

exports.login = function (req, res) {
    var dblessPrototype = true;
    // get email address from req.body, trim it and lowercase it
    var email = req.body.emailAddress ? 
        req.body.emailAddress.trim().toLowerCase() :
        "";
    // begin the login process
    async.waterfall([
        // prelimninary verification: make sure email,password are not empty, and that email is of valid format
        function(cb) 
        {
            // if no email address
            if (!email)
            {
                // send error via cb
                cb(helpers.missingData("email_address"));
            }
            // if '@' not found in email address
            else if (email.indexOf('@') == -1)
            {
                // then email address is invalid
                cb(helpers.invalidData("email_address"));
            }
            // if password is missing from req.body
            else if (!req.body.password)
            {
                // tell next function about that
                cb(helpers.missingData("password"));
            }
            // we are ready to move on otherwise
            else cb(null);
        },
        // TODO: lookup by email address
        // check the password
        function (userData, cb)
        {
            var u;
            if (dblessPrototype)
            {
                u = new User(0, 
                    "[email protected]",
                    "SampleAdmin",
                    "Sample0x50617373");
            }
            else 
            {
                u = new User(userData);
            }
            console.log("u._password == " + u._password);
            console.log("req.body.password == " + req.body.password);
            u.checkPassword(req.body.password, cb);
        },
        // time to set status of authenticiation
        function (authOK, cb)
        {
            console.log("authOK == " + authOK);
            if (!authOK)
            {
                cb(helpers.authFailed());
                return;
            }

            // set status of authenticiation in req.session
            req.session.loggedIn = true;
            req.session.emailAddress = req.body.emailAddress;
            req.session.loggedInTime = new Date();
        }
    ],
    function (err, results)
    {
        if (err)
        {
            console.log(JSON.stringify(err, null, '\t'));
            if (err.code != "already_logged_in")
            {
                helpers.sendFailure(res, err);
                console.log("Already logged in...");
            }
        }
        else
        {
            helpers.sendSuccess(res, { loggedIn: true });
            console.log("Log in successful...");
        }
    });

}

In the checking of the password, u._password is null (it never gets set, which means that the asynchronous bcrypt.genSalt() code isn't getting called. Also, neither the last function in the array that is the first parameter of async.waterfall() nor the function that is the last parameter of async.waterfall() are getting called.

Why aren't these functions getting invoked, and what can I do about it?

EDIT: I made the asynchronous password encryption synchronous, by replacing

bcrypt.genSalt(10, function (err, salt) {
        // this, for some reason, isn't getting called. Literally, I never see "I'm here"
        console.log("I'm here...");
        bcrypt.hash(password, salt, function (err, hash) { 
            if (!err)
            {
                this._password = hash;
                console.log("this._password == " + this._password);
            }
            else
            {
                console.log("Error occurred: ");
                console.log(err);
            }
        })
    });

with this._password = bcrypt.hashSync(password, bcrypt.genSaltSync(10));

It gets to the password-comparison part, hangs for a while, and then to the next (the last) element of the array, without printing anything to the console. It's as if that element got skipped.

Upvotes: 0

Views: 112

Answers (1)

alex
alex

Reputation: 5583

EDIT after downloading the full app and messing around with it at home.

I changed your code to include a setTimeout method and also enforced the this context in the User function. Running all the code on my own machine which I downloaded from your git repo I got it to the point where user is authenticated and the app looks for index.html which is not there. So the authentication is working!

The code is not waiting for the salt and hash to finish before continuing. If you were writing to a db you could do this 'pre' save and use a promise. But this will give you a work around for now.

function (cb, userData)
    {
        var u;
        if (dblessPrototype)
        {
          var authOK;
            u = new User(0,
                "[email protected]",
                "SampleAdmin",
                "Sample0x50617373");
                setTimeout(function () {
                  console.log("u._password == " + u._password);
                  console.log("req.body.password == " + req.body.password);
                  console.log(u)
                  u.checkPassword(req.body.password, function(err, res) {
                    if (err) {
                      console.log(err)
                    } else {
                      console.log(res)
                      // USER AUTHENTICATED
                      cb(null, true)
                    }
                    return res;
                  });
                }, 1000)
        }
        else
        {
            u = new User(userData);
    }
}

Assign this to self inside the User object generator:

function User(id, email, displayName, password, deleted)
{
    this.userID = id;
    this.email = email;
    this.displayName = displayName;
    var self = this
    if (User.connectedToDatabase) this._password = password;
    else
    {
        bcrypt.genSalt(10, function (err, salt) {
            // this, for some reason, isn't getting called. Literally, I never see "I'm here"
            console.log("I'm here...");
            bcrypt.hash(password, salt, function (err, hash) {
                if (!err)
                {
                    self._password = hash;
                    console.log("this._password == " + self._password);
                }
                else
                {
                    console.log("Error occurred: ");
                    console.log(err);
                }
            })
        });
    }
    // this._password = password;
    this.deleted = deleted;
}

I should add that using the setTimeout is not a recommended approach, but a way of demonstrating what the problem is. Ideally this should be done using a callback, promise, or generator.

Upvotes: 1

Related Questions