Alexandre Bourlier
Alexandre Bourlier

Reputation: 4118

user object not loaded after logging out and quickly logging back in in Meteor

I am facing a strange behaviour, and can't debug it properly. I need your help.

If I log out of Meteor, everything seems fine. If I wait for around 2 seconds and log back in again, everything is still very fine. But if I logout and quickly login again, right after the login process, the Meteor.user() object is set to null, which leads my router to redirect the user back to the login page.

Any idea why this is happening, and how could I prevent it?

I have spent 2h trying several things without success. Any suggestion is most welcome.

EDIT

This is my global onBeforeAction function :

Router.onBeforeAction(function() {
  // Ensures the user is logged in
  if (!Meteor.userId()) {
    please_login();
  }

  // Email address not verified for 24h? Please verify it!
  else {
   var self = this;

   // Waits for the user object to be passed over DDP
   function wait_for_user_data() {
     if (Meteor.user() && Meteor.user().emails && Meteor.user().profile) {
       var user  = Meteor.user();
       var now   = new Date().getTime();
       var ca    = user.createdAt.getTime();// Created At
       var cs    = (now - ca) / (24 * 60 * 60 * 1000);// Created Since (in days)
       var urls  = ["email_verification_required", "email_verification"];

       if (cs > 1 && 
         !user.emails[0].verified && 
         urls.indexOf(self.url.split("/")[1]) == -1) {
         Router.go("email_verification_required");
       }

       else {
         self.next();
       }
     }

     else {
       setTimeout(wait_for_user_data, 500);
     }
   }
    wait_for_user_data();
  }
}, 
{except: ['home', 'login', 'register', 'password_recovery', "email_verification", "profile"]})

What actually happens is the following :

When I login right after having logged out, self.next() is called, but the current user properties (Meteor.user().emails and Meteor.user().profile) aren't loaded yet for some reason. They are undefined. As you can see, I tried to work around this by waiting until they are defined, but then I receive the following error message :

Route dispatch never rendered. Did you forget to call this.next() 
in an onBeforeAction?

This seems to cause Meteor to set Meteor.user() to null, and so my user gets redirected to the login page...

EDIT BIS

This is how I am handling the publish/subscribe of the users data. I have 2 different pub/sub set, one for all users, and the other one for the logged in user only.

Meteor.publish('users', function() {
  var args   = {};
  var fields = {
    '_id':                1, 
    'createdAt':          1,
    'profile.firstname' : 1, 
    'profile.lastname' :  1, 
    'profile.lang' :      1, 
  };

  // Only admins can access those sensible information
  if (this.userId && Roles.userIsInRole(this.userId, 'admin')) {
    fields["emails"]          = 1;
    fields["profile.celular"] = 1;

    args : {$ne : {_id: this.userId}};
  }

  return Meteor.users.find(args, {fields: fields});
});
Meteor.publish('user', function() {
  return Meteor.users.find({_id: this.userId});
});

This is how I subscribe to those two publications within my router's configurations :

Router.configure({
  layoutTemplate:   'layout',
  loadingTemplate:  'loading', 
  waitOn: function() { 
    return [Meteor.subscribe('user'), Meteor.subscribe('users')]; 
  },
});

Upvotes: 2

Views: 86

Answers (2)

Michel Floyd
Michel Floyd

Reputation: 20246

The way I've deal with this in the past is to define an explicit subscription to the user object then waitOn that subscription in the router. Much simpler than writing a polling loop but mostly I did it because I needed specific fields that didn't come over by default. Hopefully this can help you with your problem.

Server:

Meteor.publish('me',function(){
  if ( this.userId ){
     // return the fields I need
    return Meteor.users.findOne({ _id: this.userId },{ fields: { field1: 1, field2: 1, ... }});
  }
  else this.ready();
});

Router:

waitOn: function(){
  return Meteor.subscribe('me');
}

Upvotes: 1

bigmadwolf
bigmadwolf

Reputation: 3519

Without a code example (and knowing which router you're using) I'm going to take a guess that you're router code looks something like :

 if (!Meteor.user()) {
      //... redirect or change tempalte etc
 }

There is another value you can check,

 if (!Meteor.user() && !Meteor.loggingIn()) {
      //... redirect or change tempalte etc
 }

you can use various combinations of these to handle the login state, such as having a logging in loading view etc

With more information

The redirect is being called in a callback, currently for the login with password function, having a look at the docs it should be called on the onLogin callback linked as this ensures that login was successful, though doing the redirect here, may not provide the same range of control as doing this in a reactive context such as a before hook on a controller, or an autorun function created in the login template onCreated callback.

Upvotes: 1

Related Questions