2

The problem I was initially trying to solve was to redirect a user to the login page if they are not logged in and vice versa.

I did this with the following code

.run(function($rootScope, $http, AppService, $state) {
    $rootScope.$on('application:refreshtoken', function(rootScope, token) {
        if(token) {
            $http.defaults.headers.common['X-Auth-Token'] = token;
            AppService.setAuthToken(token);
            AppService.resetLoginTimeout();
        }
    });
    $rootScope.$on('$stateChangeSuccess', function() {
        $http.get('/api/heartbeat');
    });

    // This is the really pertinent bit...
    $rootScope.$on('$stateChangeStart', function(e, toState) {
        if(toState.name === 'login') {
            if(AppService.getIsLoggedIn()) {
                e.preventDefault();
                $state.go(AppService.getRedirectPage());
            }
        } else {
            if(!AppService.getIsLoggedIn()) {
                e.preventDefault();
                $state.go('login');
            }
        }
    });
 });

AppService

.factory('AppService', ['$rootScope', 'locker', '$http', '$state',
  function ($rootScope, locker, $http, $state) {

    var _isLoggedIn = locker.get('loggedIn', false),
      _authToken = locker.get('authtoken', null),
      _roles = locker.get('roles', null),
      _permissions = locker.get('permissions', null),
      _user = locker.get('user', null),
      _userid = locker.get('userid', null),
      _userprefs = locker.get('userprefs', null),
      _timedout,
      _timeoutId,
      service = {};

    if (_authToken) {
      $http.defaults.headers.common['X-Auth-Token'] = _authToken;
    }

    service.setIsLoggedIn = function (isLoggedIn) {
      _isLoggedIn = isLoggedIn;
      this.doLogin();
      broadcastLogin();
    };

    service.doLogin = function () {
      if (_isLoggedIn) {
        locker.put({
          loggedIn: _isLoggedIn,
          authtoken: _authToken,
          roles: _roles,
          permissions: _permissions,
          user: _user,
          userprefs: _userprefs
        });
      }
    };

    service.doLogout = function (cb) {
      _isLoggedIn = false;
      _authToken = null;
      _roles = null;
      _permissions = null;
      _user = null;
      _userid = null;
      _userprefs = null;

      delete $http.defaults.headers.common['X-Auth-Token'];

      locker.clean();

      cb();
    };


    service.getIsLoggedIn = function () {
      return _isLoggedIn;
    };

    service.setAuthToken = function (authToken) {
      _authToken = authToken;
      locker.put({
        authtoken: _authToken
      });

    };

    service.getAuthToken = function () {
      return _authToken;
    };

    service.setUserid = function (userid) {
      locker.put('userid', userid);
      _userid = userid;
    };

    service.getUserid = function () {
      return _userid;
    };

    service.setUser = function (user) {
      _user = user;
    };

    service.getUser = function () {
      return _user;
    };

    service.setRoles = function (roles) {
      _roles = roles;
    };

    service.getRoles = function () {
      return _roles;
    };

    service.setPermissions = function (permissions) {
      _permissions = permissions;
    };

    service.getPermissions = function () {
      return _permissions;
    };

    service.setUserPreferences = function (prefs) {
      _userprefs = prefs;
    };

    service.getUserPreferences = function () {
      return _userprefs;
    };

    service.resetLoginTimeout = function () {
      if (_timeoutId) {
        clearTimeout(_timeoutId);
      }
      _timeoutId = setTimeout(function () {
        $rootScope.$broadcast('application:logintimeoutwarn');
      }, 1000 * 60 * 4);
    };

    service.setTimedOut = function (timedout) {
      _timedout = timedout;
    };

    service.getTimedOut = function () {
      return _timedout;
    };

    service.extendSession = function () {
      $http.get('/api/heartbeat');
    };

    service.goDefaultUserPage = function () {
      var success = false;
      if (_userprefs.landingPage) {
        $state.go(_userprefs.landingPage);
        success = true;
      } else {
        var permissionRoutes = {
          'regimens': 'regimens.do',
          'pathways': 'pathways',
          'manage.users': 'manageusers.do',
          'manage.practices': 'managepractices.do',
          'patients': 'patients'
        };
        _.some(_permissions, function (el) {
          var state = $state.get(permissionRoutes[el]);
          if (!state.abstract) {
            $state.go(state.name);
            success = true;
            return true;
          }
        });
      }
      return success;
    };

    service.getRedirectPage = function () {
      var page = false;
      if (_userprefs.landingPage) {
        page = _userprefs.landingPage;
      } else {
        var permissionRoutes = {
          'regimens': 'regimens.do',
          'pathways': 'pathways',
          'manage.users': 'manageusers.do',
          'manage.practices': 'managepractices.do',
          'patients': 'patients'
        };
        _.some(_permissions, function (el) {
          var state = $state.get(permissionRoutes[el]);
          if (!state.abstract) {
            page = state.name;
            return true;
          }
        });
      }
      return page;
    };

    function broadcastLogin() {
      $rootScope.$broadcast('application:loggedinstatus');
    }

    broadcastLogin();

    return service;

  }
])

This code works great until I take a very specific set of actions:

  1. Login
  2. Close the open tab or window
  3. Open a new tab and go to the application

Since I am still logged in to the application, I have a user object and a valid token, but I am getting error:infdig Infinite $digest Loop. It eventually resolves and goes to the correct state, but it takes a while and the path flickers (I can post a video if needed).

I tried using $location.path instead of $state.go in the $rootScope.$on('$stateChangeSuccess') callback, but the issue persists.

This doesn't really affect the functioning of the application, but it is annoying. I also don't really want to change my locker storage to session storage because I want the user to stay logged in if they close the tab and reopen.

daniel0mullins
  • 1,937
  • 5
  • 21
  • 45

2 Answers2

4

I would say, that the issue is hidden in the improper if statements inside of the $rootScope.$on('$stateChangeStart'... Check this:

With a general suggestion:

let's redirect ($state.go()) only if needed - else get out of the event listener

$rootScope.$on('$stateChangeStart' ...
  if (toState.name === 'login' ){
    // going to login ... do not solve it at all
    return;
  }

Second check should be: is user authenticated (and NOT going to login)?

if(AppService.getIsLoggedIn()) {
  // do not redirect, let him go... he is AUTHENTICATED
  return;
}

Now we have state, which is not login, user is not authenticated, we can clearly call:

// this is a must - stop current flow
e.preventDefault();
$state.go('login'); // go to login

And all will work as we'd expected

Very detailed explanation and working example could be also found here...

Community
  • 1
  • 1
Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
  • Ok, but I do not want them going to the login page if they are already logged in, should I just add logic to the LoginCtrl that redirects them if they are already logged in and take it OUT of the `$stateChangeStart` listener? – daniel0mullins Jan 30 '15 at 14:55
  • I would say no. Please, check my answer in detail. I say: if user is already navigating to some other rout... and is `AppService.getIsLoggedIn()` - let him go to that state. ONLY in case, he is not authenticated and he is not login... he should be redirected. Do you know what I mean? Does it help now? this way I use it long time and it is working... Simply after user is Logged in... you should call $state.go("anywhere") and solution in my answer will NOT stop it ;) – Radim Köhler Jan 30 '15 at 14:58
1

this usally happens when the app gets stuck between a route rejection through a resolve clause and an automatic redirection on the previous route where the landing page will redirect to some page, say auth, and the auth page needs some conditions to let you in and if it fails or it will redirect back to some other page, hence the cycle, make sure you get your story straight and if needed use an intermediate state to clear all preferences and take the default path

Dayan Moreno Leon
  • 5,357
  • 2
  • 22
  • 24