4
votes

I am currently developing an API. I am using Strongloop (Loopback).

I am trying to implement email verification for when a user registers. I have a "user" model which extends the built-in "User" model.

Right now, when the user registers (POST /users), an email is sent with a link to /users/confirm with the three appropriate parameters i.e. uid, redirect and a token. When the user clicks on the link, the latter's email address is correctly verified (emailVerification field becomes true).

However, I've noticed that when making a POST request on /users, the response contains the verification token. Is this normal? Isn't the verification token be accessible only via the sent email?

Because as it is, by making a POST request on /users and getting the verification token in the response, one can easily send another request to users/confirm with the appropriate parameters (which includes the verification token) and verify the email address even if the address doesn't exist.

I am new to Strongloop and maybe I'm missing something. Can you guys help?

3
I am building an example repo for these types of questions as it comes up often. You can see what I have now at github.com/strongloop/loopback-faq-user-management. It's not done yet as I'm currently working on it, but I hope to finish by Friday. - superkhau
That sounds great. Looking forward to it. - user2483431
Example is done, please upvote the answer. - superkhau

3 Answers

2
votes

if you want to create user registration verification link but not use the built in user moder then you need to create verification token and then send the link.i have added the two methods.you need to use also remote method with one argument who is object type:the you need to send the parameter with register email.the method is like this....here we use the user=any variable name,modelN=model name

modelN.sendVerificationEmail = function (data, callback) {
  var user = loopback.models.modelN;

  if (!data.email) {
    return callback(commonUtils.buildError(
      'Recipient email is required.', 400, 'EMAIL_REQUIRED'
    ));
  }

  if (!emailValidator.validate(data.email)) {
    return callback(commonUtils.buildError(
      'Must provide a valid email.', 400, 'INVALID_EMAIL'
    ));
  }

  var findOneuserPromise
    = modelN.findOne({ 'where': { 'email': data.email }});

  findOneBusinessEmployeePromise.then(function (user) {
    if (!user) {
      return callback();
    }

    var sendVerificationEmailPromise
      = anothermodelname.sendVerificationEmail(user.id);

    sendVerificationEmailPromise.then(function () {
      callback();
    });

    sendVerificationEmailPromise.then(null, function (error) {
      callback(error);
    });
  });

  findOneuserPromise.then(null, function (error) {
    callback(error);
  });
};

i have add another model model method after then add this,....

anothermodelname.sendVerificationEmail = function (userid) {
  var modelN = loopback.models.modelN;
  var Email = loopback.models.Email;
  var deferred = Q.defer();
  var findByIduserPromise = modelN.findById(userId);

  findByIduserPromise.then(function (user) {
    if (!user) {
      return deferred.reject(commonUtils.buildError(
        'Unknown "modelN" id "' + userId + '".',
        404, 'MODEL_NOT_FOUND'
      ));
    }
    if (!user.verificationToken) {
      return deferred.resolve(true);
    }


    modelN.generateVerificationToken(user,
      function (verificationTokenError, verificationToken) {
        if (verificationTokenError) {
          return deferred.reject(verificationTokenError);
        }

        user.verificationToken = verificationToken;

        var saveuserPromise = user.save();

        saveuserPromise.then(function (updateduser) {
          var link = emailConf.clientBaseUrl +
            emailConf.verifyEmailRedirect + '?uid=' +
            updateduser.id + '&token=' +
            updateduser.verificationToken;
               console.log("check+link:",link);
          /*eslint camelcase: [0, {properties: "never"}]*/
          emailOptions.to = updateduser.email;
          emailOptions.template = { 'name': 'verify' };
          emailOptions.global_merge_vars = [];

          emailOptions.global_merge_vars.push({
            'name': 'USER_NAME',
            'content': updateduser.name
            || updateduser.username || updateduser.email
          });

          emailOptions.global_merge_vars.push({
            'name': 'LINK',
            'content': link
          });
          Email.send(emailOptions, function () {});

          deferred.resolve(true);
        });

        saveuserPromise.then(null, function (error) {
          deferred.reject(error);
        });
      });
  });

  findByIduserPromise.then(null, function (error) {
    deferred.reject(error);
  });

  return deferred.promise;
};
1
votes

@user2483431, verify email exposes the email verification token in response along with user id. As you correctly pointed, it risks fooling the system. One solution is to strip off the token value from response.

Inside the afterRemote for user create, you can use,

user.verify(options, function(err, response, next2) {
  if (err) {
    // error handling code
  }
  // stripping off verificationToken from response for security
  var replacementText = "check email"
  context.result.verificationToken = replacementText;
  next();
});

Hope this helps!