Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

getCurrentUser().role undefined in DaftMonk/generator-angular-fullstack

Tags:

angularjs

We are using the DaftMonk/AngularJS Full-Stack generator for a projet. The generated code contains the logic to manage users (registration, login, roles, etc.), which is great. However, there is an issue when using the code and the role of the current user is sometimes undefined.

We encountered the issue as we wanted to implement a simple feature: after login/signup, the user should be redirected to a URL that depends on his role. For instance, 'teacher' users should be redirected to /teacherHome and 'student' users should be redirected to /studentHome. Because Auth.getCurrentUser().role is undefined, the logic does not work.

How to reproduce the issue

To reproduce the issue, edit the client/app/account/login/login.controller.js file and add statements to log the value of Auth.getCurrentUser().role, like so:

...
  if(form.$valid) {
    Auth.login({
      email: $scope.user.email,
      password: $scope.user.password
    })
    .then( function() {
      // Logged in, redirect to home
      console.log("Current user role: " + Auth.getCurrentUser().role);
      $location.path('/');
    })
    .catch( function(err) {
      $scope.errors.other = err.message;
    });
  }
};
...

When testing this code, you will find out that the user role is undefined at this stage. Later on, however, it is assigned a value. This suggests that the issue might be related to an asynchronous REST call.

like image 726
Olivier Liechti Avatar asked Jan 05 '15 15:01

Olivier Liechti


1 Answers

There are actually a couple of related issues and ways to fix them.

Solution 1: use the provided Auth.isLoggedInAsync function

The Auth module, part of the generated code, provides a method to check if the login process has been completed (and to invoke a callback function when this is the case). So, one way to fix the issue is to use this function in the client code, like so:

  if(form.$valid) {
    Auth.login({
      email: $scope.user.email,
      password: $scope.user.password
    })
    .then( function() {
      // Logged in, redirect to home
      console.log("Current user role: " + Auth.getCurrentUser().role);
      Auth.isLoggedInAsync(function(success) {
        console.log("Current user role: " + Auth.getCurrentUser().role);
      });
      $location.path('/');
    })
    .catch( function(err) {
      $scope.errors.other = err.message;
    });
  }
};

In this case, you will see two statements in the console. The first one will show that Auth.getCurrentUser().role is still undefined. The second one will show that it now has a value. So, if you have logic that you want to execute at login time and that depends on the user role (or other user attributes), put this logic in a callback function that you pass to Auth.isLoggedInAsync().

Solution 2: fix the root cause in the auth.service.js

If you look at the code in client/components/auth/auth.service.js, you will see that there is an issue with asynchronous code. The problem is that currentUser = User.get(); triggers an asynchronous HTTP call, and that currentUser is not immediately set (it is a non-blocking call). Since the promise is resolved immediately, clients are lead to believe that the login process has completed and that all user data is available, whereas it is in fact still in flight.

In the proposed fix, the promise is resolved in a callback function passed to User.get().

  /**
   * Authenticate user and save token
   *
   * @param  {Object}   user     - login info
   * @param  {Function} callback - optional
   * @return {Promise}
   */
  login: function (user, callback) {
    var cb = callback || angular.noop;
    var deferred = $q.defer();

    $http.post('/auth/local', {
      email: user.email,
      password: user.password
    }).
    /* ORIGINAL CODE -- promise is resolved too early
    success(function (data) {
      $cookieStore.put('token', data.token);
      currentUser = User.get();
      deferred.resolve(data);
      return cb();
    }).
    */
    /* PROPOSED FIX -- promise is resolved once HTTP call has returned */
    success(function (data) {
      $cookieStore.put('token', data.token);
      currentUser = User.get(function() {
        deferred.resolve(data);
        return cb();
      });
    }).
    error(function (err) {
      this.logout();
      deferred.reject(err);
      return cb(err);
    }.bind(this));

    return deferred.promise;
  },

Related issue and fix

The proposed code fixes one issue. It is now possible to redirect the user to a specific page upon successful login. However, a similar problem arises after the signup procedure. In this case also, Auth.getCurrentUser().role is undefined for a while (long enough for the redirection logic to fail).

In this case, the code was fixed as follows:

  /**
   * Create a new user
   *
   * @param  {Object}   user     - user info
   * @param  {Function} callback - optional
   * @return {Promise}
   */
  createUser: function (user, callback) {
    var cb = callback || angular.noop;

     /* ORIGINAL CODE ---------------------
     return User.save(user,
      function(data) {
        $cookieStore.put('token', data.token);
        currentUser = User.get();
        return cb(user);
      },
      function(err) {
        this.logout();
        return cb(err);
      }.bind(this)).$promise;
       --------------------- */

    /* PROPOSED FIX --------------------- */
    var deferred = $q.defer();
    User.save(user,
      function (data) {
        $cookieStore.put('token', data.token);
        currentUser = User.get(function () {
          console.log('User.save(), user role: ' + currentUser.role);
          deferred.resolve(data);
          return cb(currentUser);
        });
      },
      function (err) {
        this.logout();
        return cb(err);
        deferred.reject(err);
      });
    return deferred.promise;

  },
like image 113
Olivier Liechti Avatar answered Sep 28 '22 14:09

Olivier Liechti