0

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, 
                    "admin@mikewarren.me",
                    "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.

Community
  • 1
  • 1
Mike Warren
  • 3,796
  • 5
  • 47
  • 99

1 Answers1

1

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,
                "admin@mikewarren.me",
                "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.

alex
  • 5,467
  • 4
  • 33
  • 43
  • What version of node are you running? I am running in bash so I am unfamiliar with that environment. It looks like your app is attempting to import JQuery? – alex Jul 30 '16 at 15:36
  • Not sure the version of node that I am running, and I am also running in bash. Yes, my app is importing jQuery (client-side). – Mike Warren Jul 30 '16 at 15:40
  • Try shortening the interval and see what happens, at the moment it is 5 seconds and 2 will probably do. If it is possible then perhaps update your version of node. Have a look at this as well, http://stackoverflow.com/questions/31424223/node-js-crashes-when-using-long-interval-in-setinterval – alex Jul 30 '16 at 15:46
  • Yes, console logged an encrypted password. – alex Jul 30 '16 at 15:56
  • Decreasing the interval size was a placebo, and updating node made it so that I couldn't run my app at all. – Mike Warren Jul 30 '16 at 16:10
  • Sorry, got it working at my end let me know how it goes. – alex Jul 30 '16 at 16:12
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/118705/discussion-between-mike-warren-and-alexi2). – Mike Warren Jul 30 '16 at 16:13