Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to allow user login by either username or email using node.js

I have a node.js login system with passport but I am trying to figure out how to log in a user with either their username or email. I am only able to log in the user with email or username seperately. I don't know how to write the code to cater for both their username and email. So if a user wants to login with username, they can or if the wish to use their email, they also can. Here is my localstrategy code in my users.js file:

passport.use(new LocalStrategy(
    function(email, password, done) {
        User.getUserByEmail(email, function(err, user, next){
            if(err) throw err;
            if(!user){
                return done(null, false, {message: 'Unknown user'});
            }

        User.comparePassword(password, user.password, function(err, isMatch){
            if(err) throw err;
            if(isMatch){
                return done(null, user);
            } else {
                return done(null, false, {message: 'Invalid password'});
            }
        });
        });
    }));

And here's my module.exports in my user.js:

module.exports.getUserByEmail = function(email, callback){
    var query = {email: email};
    User.findOne(query, callback);
}

module.exports.getUserById = function(id, callback){
    User.findById(id, callback);
}

module.exports.comparePassword = function(candidatePassword, hash, callback){
    bcrypt.compare(candidatePassword, hash, function(err, isMatch) {
        if(err) throw err;
        callback(null, isMatch);
    });
}

The above code only allows user to login with their email. I want users to have the opportunity to login with either their email or username.

like image 562
Stephen Avatar asked Jan 06 '18 09:01

Stephen


1 Answers

Expect both username and password within your authentication middleware and then proceed with whatever value you have found as the condition to find the user.

Middleware example:

function authenticateUser(req, res, done) {

  let username = req.body.username,
      password = req.body.password,
      email    = req.body.email;
  let conditions = !!username ? {username: username} : {email: email};

  UserModel.findOne(conditions, (err, user) => {
    if (err)   return done(err);
    if (!user) return done(new Error('Incorrect username or email'));

    return user.comparePassword(password, user.password)
    .then(match => {
      if (match) return done();
      else       return done(new Error('Incorrect password'));
    })
    .catch(error => {
      if (error) return done(new Error(`Unable to validated password. - ${error}`));
    });
  });

}

Now, a front-end developer — with the right documentation — can now actually use either the username, email or both (you will need a bit of JavaScript for both) when building login forms using your endpoint. Here is an example of using both:

HTML:

<form id="login-form" method="POST" action="/login">
  <input id="username-or-email" type="text" placeholder="Username or Email" required/>
  <input type="password" name="password" placeholder="Password"  required/>
  <input type="submit"/>
</form>

JavaScript:

// select the form element
let loginForm = document.querySelector('#login-form');

// add a form handler on submit
loginForm.addEventListener("submit", formHandler);

// validate and the set name attribute as appropriate
function formHandler() {
  /** W3C Email regex: (RFC5322) */
  const email_regex = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
  /** Must starts with a letter then can include underscores (_) & hyphens (-) */
  const username_regex = /^[a-zA-Z][\w-]+$/;

  let input = document.querySelector('#username-or-email');

  if (email_regex.test(input.value)) {
    // it is an email, send the value as an email
    input.setAttribute("name", "email");
  } else if (username_regex.test(input.value)) {
    // it is a username, send the value as a username
    input.setAttribute("name", "username");
  } else {
    // invalid email or username format, return an error message to user
  }

}

So you get to validate and set your dynamic input at the same time. Keep in mind that the regular expression should match your username and email data model as much as possible.

like image 115
U-ways Avatar answered Oct 31 '22 22:10

U-ways