Verify user using 'after save' operation hook

878 kali dilihat
Langsung ke pesan pertama yang belum dibaca

Assaf Koren

belum dibaca,
17 Agu 2015, 04.22.1217/08/15
kepadaloopb...@googlegroups.com
Hi, I'm trying to implement the user verification functionality from the loopback-faq-user-management example while using the new hooks method, I am using Postgres so i override the lack of isNewInstance() by using a created attribute.
My problem is that once the verify function is invoked, it enters an infinite loop, same goes for every other model attribute change in this contex, like the update of 'created' and 'lastUpdated' at the bottom.

My code is: (Ekouser extends user)

Ekouser.observe('after save', function(context, next) {

   
Ekouser = context.instance;

   
//console.log('> user.after save triggered, context :', context.method.name);
   console
.log('> user.after save triggered, Created :', Ekouser.created);

   currDate
= new Date();

   
if (!Ekouser.created){

     console
.log('> New user, sending verification Email on ', currDate, " for ", Ekouser.email );

       
var options = {
         type
: 'email',
         to
: Ekouser.email,
         
from: 'te...@gmail.com',
         subject
: 'Thanks for registering.',
         
template: path.resolve(__dirname, '../../server/views/verify.ejs'),
         redirect
: '/verified',
         user
: Ekouser
       
};

       
Ekouser.verify(options, function (err, response) {
         
if (err) {
           console
.log(err);
           
return;
         
}

         console
.log('> verification email sent:', response);

         context
.res.render('response', {
           title
: 'Signed up successfully',
           content
: 'Please check your email and click on the verification link '
           
+ 'before logging in.',
           redirectTo
: '/',
           redirectToLinkText
: 'Log in'
         
});

         
// update create and update time
         
Ekouser.updateAttributes({created: currDate ,lastUpdated: currDate  }, function (err, response) {
           
if (err) {
             console
.log(err);
             
return;
           
}
         
});
         
next();
       
});
   
}
   
else next();
 
});

Is there anyway to avoid this? 
I suppose it would also be a good idea to update the loopback-faq-user-management example to use the new approach

Cheers
Assaf

Assaf Koren

belum dibaca,
20 Agu 2015, 13.46.1020/08/15
kepadaLoopbackJS
Bump Bump, any ideas anyone? updating model fields after save should be strait forward..

Thanks

rites...@anthropower.in

belum dibaca,
25 Agu 2015, 07.30.5625/08/15
kepadaLoopbackJS
I tried with this code and it doesn't give an infinite loop

Product.observe('before save', function (context, next) {

 
//context.instance is available when saving new model
 
//and context.data is available when updating some of the fields of the model
 
//See here: http://docs.strongloop.com/display/public/LB/Operation+hooks#Operationhooks-beforesave
 
var product = (context.instance) ? context.instance : context.data;
 console
.log('Creation date:', product.created);

 
if (!product.created) {
   
var currDate = new Date();
   console
.log('New product', currDate, " for ", product.name);

   
// update create and update time

   product
.updateAttributes({created: currDate}, function (err, response) {

     
if (err) {
       console
.log(err);
       
return;
     
}
   
});
   
next();
 
}

 
else {
   console
.log("Already created");
   
next();
 
}
});


May be this is an issue:
You are using model name when getting instance 
Ekouser = context.instance;

Instead, try
var user = context.instance

Also context.instance is not available when model instance is updated, use context.data. See my example.

I see that you are trying to send verification e-mail when user is created so why not just use a remote hook instead of operation hook
MyUser.afterRemote('create', function (context, user, next) {...}

Assaf Koren

belum dibaca,
26 Agu 2015, 08.35.1726/08/15
kepadaLoopbackJS
Hi, thank you for your answer,

In general, I'm trying to implement this using the operation hooks since the method hooks are marked as deprecated in the documentation, also for some reason ,'after create' is not triggered when i use user.create instead of using the REST, but otherwise using 'after create' does the job.

Your code will not go infinite since when hook is invoked on the second time, (!product.created) will return false and nothing else would happen.

But what if we need to perform more than one action on the model?? or call a method that will perform on the object(such as user.verify)? it would be hard to know the object state from outside the method, i would then need to know somehow that this a new user after a user.verify call in order to intercept it.

rites...@anthropower.in

belum dibaca,
31 Agu 2015, 05.32.3131/08/15
kepadaLoopbackJS

Below are my responses:

In general, I'm trying to implement this using the operation hooks since the method hooks are marked as deprecated in the documentation, also for some reason ,'after create' is not triggered when i use user.create instead of using the REST, but otherwise using 'after create' does the job.

What I suggested at the end of my previous reply was a remote hook (afterMethod('create')) and not a model hook. There isn't anything like 'after create'. You may forget about Model hooks at all and then see what would be the best option: remote hook or operation hook.
 
Your code will not go infinite since when hook is invoked on the second time, (!product.created) will return false and nothing else would happen.

But what if we need to perform more than one action on the model?? or call a method that will perform on the object(such as user.verify)? it would be hard to know the object state from outside the method, i would then need to know somehow that this a new user after a user.verify call in order to intercept it.

I think you are already sequencing multiple actions in your code above i.e. to update attributes when verify responds. And you know if user is being newly added or being updated. So I don't really understand your question.

Tal Pascovich

belum dibaca,
20 Jun 2016, 05.27.0620/06/16
kepadaLoopbackJS
Hi Assaf,
Were you able to find another solution? I'm not comfortable with it being triggered multiple times.
My solution is similar to yours. the only difference is that I'm using the built-in 'isNewInstance' and modifying it to 'false':

        var userModel = App.models.user;

        userModel.create({
            email: invitations.email,
            password: 'opensesame'
        });

        userModel.observe('after save', function (context, next) {
            if (context.isNewInstance) {
                context.isNewInstance = false;
                console.log('> user.afterRemote triggered');
                var curUser = context.instance;
                var options = {
                    type: 'email',
                    to: curUser.email,
                    from: 'nor...@loopback.com',
                    subject: 'Thanks for registering.',
                    template: path.resolve(__dirname, '../../server/views/pages/verify.jade'),
                    redirect: '/verified',
                    user: curUser
                };

                curUser.verify(options, function (err, response) {
                    if (err) return next(err);
                    console.log('> verification email sent:', response);
                });
                next();
            }
            next();
        });

Akram Shehadi

belum dibaca,
23 Jun 2016, 19.30.1323/06/16
kepadaloopb...@googlegroups.com
I'm not sure if this is of any help, but the way I implemented the User verification functionality is as follows.

Basically, as per spencermefford, who kindly provided the scaffolding code (https://gist.github.com/spencermefford/bc73812f216e0e254ad1), to make the boot script that basically overrides the User model so you can add hooks and other stuff.

So I have file called ./server/boot/000-model-override.js (the 000 is so it loads first, since boot scripts are started in alphabetical order and I have other stuff that could depend on the User model being ready) that looks like this (I removed most of the other stuff for password reset request, etc and there are some comments and other cruft that you can ignore):

module.exports = function (app) {
 
var loopback    = require('loopback');
 
var logger      = require ('../utils/logger');
 
var uuid        = require('uuid');
 
var _           = require('lodash');

 
var User            = app.models.User;
 
var Role            = app.models.Role;
 
var RoleMapping     = app.models.RoleMapping;
 
var ACL             = app.models.ACL;

 
// I removed all ACL's and moved them elsewhere.

 
// This is for the verification email
 
var config            = require('../config.json');
 
var path              = require('path');
 
//var dsConfig          = require('../datasources.json');
 
var env               = process.env.NODE_ENV;
  logger
.info('env: %s', env);
 
var dsConfig          = require('../datasources.production' );  
 
 
var serviceEmail      = dsConfig.emailDs.transports[0].auth.user;
 
var serviceEmailPort  = dsConfig.emailDs.transports[0].port;
 
var serviceEmailHost  = dsConfig.emailDs.transports[0].host;

 
var Email             = app.models.Email;
 
var hostAddress       = process.env.PROD_HOST || process.env.DEV_HOST || app.get('host') || "0.0.0.0";
 
var portNumber        = process.env.PROD_PORT || process.env.DEV_PORT || 443;
 
var mailgunKey        = process.env.MAILGUN_KEY;
 
var restApiRoot       = (app && app.get('restApiRoot')) || '/api';
 
var verifyRedirect    = process.env.VERIFY_REDIRECT || '/';

 
var request           = require('request');
 
var formData          = require('form-data');

 
var headers = {
   
'User-Agent':       'MyService API/0.1.0',
   
'Content-Type':     'multipart/form-data'
 
};


User.afterRemote('create', function(context, user, next) {
   
var userModel = User.constructor;

    logger
.verbose('> user.afterRemote triggered');
    logger
.verbose("user object:");
    logger
.verbose(user);
   
//console.log("user.email object:");
   
//console.log(user.email);
   
//console.log(JSON.stringify(user.email, null, 2));

   
var verifyLink = 'https://' +
                        hostAddress
+
                       
':' +
                        portNumber
+
                        restApiRoot
+
                       
'/Users/confirm' +
                       
'?uid=' +
                        user
.id +
                       
'&redirect=https://' + hostAddress + '/verified?user_id='+user.id;


    logger
.verbose("Link: " + verifyLink);

   
var options = {
      type
: 'email',

      mailer
: Email,
      to
: user.email,
     
from: serviceEmail,
      subject
: 'User registration confirmation',
     
template: path.resolve(__dirname, '../views/verify.ejs'),
     
//text: 'Thanks for registering! Click here for verification: \n\t{href}',
      user
: user,
      verifyHref
: verifyLink,
     
//protocol: 'https',
     
//host : hostAddress,
     
//port : portNumber,
      host
: serviceEmailHost,
      port
: serviceEmailPort
   
};

    user
.verify(options, function(err, response) {
     
if (err) {
       
next(err);
       
return;
     
}
      logger
.verbose("Account verification email sent to " + options.to);
     
next();
     
return;
   
});
 
});
}


As you can see, I am using the user.verify method in the end, so I'm not sure if this is going to be triggered several times like in your case. It hasn't in my experience, but I can't say for sure.

I think the issue in your case, where the method is NOT being triggered could be because there is an error in the way you are overriding the User model perhaps?


Finally, this is not my best code and am not particularly proud of it :) but it took me a bit to understand what I needed to do and so it's probably doable in a much better way, however I think the idea is useful and might even give you a few hints on how to solve the issue hopefully.

Akram
Balas ke semua
Balas ke penulis
Teruskan
0 pesan baru