Plugging a custom service to UI Route

272 views Asked by At

I have developed an app which uses succesfully ADAL JS library for authentication with azure. However I also need to implement authorization, and I meant that I need to restrict views to specific groups.

I already have a REST API which given a user id or email can return me the groups he belongs to.

However I am not sure how to plug an angular service to cosume that REST API and plug that into the routes configuration.

app.js

(function () {
    angular.module('inspinia', [
        'ui.router',                    // Routing
        'oc.lazyLoad',                  // ocLazyLoad
        'ui.bootstrap',                 // Ui Bootstrap
        'pascalprecht.translate',       // Angular Translate
        'ngIdle',                       // Idle timer
        'AdalAngular',                  // ADAL JS Angular
        'ngRoute'                       // Routing
    ])
})();

config.js

function config($stateProvider, $urlRouterProvider, $ocLazyLoadProvider, IdleProvider, KeepaliveProvider,adalAuthenticationServiceProvider, $httpProvider) {

    // Configure Idle settings
    IdleProvider.idle(5); // in seconds
    IdleProvider.timeout(120); // in seconds

    $urlRouterProvider.otherwise("/dashboards/dashboard_1");

    $ocLazyLoadProvider.config({
        // Set to true if you want to see what and when is dynamically loaded
        debug: false
    });

    $stateProvider

        .state('dashboards', {
            abstract: true,
            url: "/dashboards",
            templateUrl: "views/common/content.html",
        })
        .state('dashboards.dashboard_1', {
            url: "/dashboard_1",
            templateUrl: "views/dashboard_1.html",
            requireADLogin: true,
            resolve: {
                loadPlugin: function ($ocLazyLoad) {
                    return $ocLazyLoad.load([
                        {

                            serie: true,
                            name: 'angular-flot',
                            files: [ 'js/plugins/flot/jquery.flot.js', 'js/plugins/flot/jquery.flot.time.js', 'js/plugins/flot/jquery.flot.tooltip.min.js', 'js/plugins/flot/jquery.flot.spline.js', 'js/plugins/flot/jquery.flot.resize.js', 'js/plugins/flot/jquery.flot.pie.js', 'js/plugins/flot/curvedLines.js', 'js/plugins/flot/angular-flot.js', ]
                        },
                        {
                            name: 'angles',
                            files: ['js/plugins/chartJs/angles.js', 'js/plugins/chartJs/Chart.min.js']
                        },
                        {
                            name: 'angular-peity',
                            files: ['js/plugins/peity/jquery.peity.min.js', 'js/plugins/peity/angular-peity.js']
                        }
                    ]);
                }
            }
        })

Ideally it would be awesome i could add a new property to each state called Groups with values:

Something like:

 url: "/dashboard_1",
                templateUrl: "views/dashboard_1.html",
                requireADLogin: true,
                groups: "Admin, Accounting, Marketing"

and then the custom service under the hood will validate this.

1

There are 1 answers

6
glennanthonyb On BEST ANSWER

I'm not familiar specifically with ADAL.js, but assuming you can say to the server, "does this user have any of these roles" in a http request, then you could intercept the $stateChangeStart, prevent the state change by calling event.preventDefault(), ask the server if the current user is in any of roles specified for in the toState as having access and then take action either way - send to an access denied page, or continue on to the toState.

The following is a flawed implementation. The working version is locked away at work, and I'm at home right now, but hopefully it will give you some ideas.

(function (app) {
  "use strict";

  app.run(run);

  function run($rootScope, $log, $state, $q, authService) {

    /**
     * The canceller is passed to the authService.isTokenValid()
     *
     * The canceller is a promise, that, when resolved, cancels the current
     * token validation request
     */
    let _canceller;

    /**
     * When state is changed, ensure that the current user has access
     * @param event
     * @param toState
     */
    function onStateChangeStart(event, toState, toParams) {

      // When token is valid, continue with navigation to the state
      function onValidToken() {
        if (toState.name === 'login'){
          $state.go('home');
        } else {
          $state.go(toState, toParams, {notify: false}).then((state) => {
            $rootScope.$broadcast('$stateChangeSuccess', state, null);
          });
        }
      }

      // When the token is not valid, set state to login
      function onInvalidToken() {
        if (toState.name === 'login'){
          $state.go(toState, toParams, {notify: false}).then((state) => {
            $rootScope.$broadcast('$stateChangeSuccess', state, null);
          });
        } else {
          $log.warn(`Access denied to state ${toState.name}`);
          $state.go('login');
        }
      }

      // On completion of token validation, resolve the canceller
      function onFinally() {
        if (_canceller) {
          _canceller.resolve();
        }
      }

      // If the state requiresLogin
      if (toState.requiresLogin || toState.name === 'login') {

        // stop navigation
        event.preventDefault();

        // Cancel any current requests
        if (_canceller) {
          _canceller.resolve();
        }

        // create a new promise
        _canceller = $q.defer();

        // validate
        authService.isTokenValid(_canceller)
          .then(onValidToken, onInvalidToken)
          .finally(onFinally);
      }
    }

    $rootScope.$on('$stateChangeStart', onStateChangeStart);



  }

}(angular.module('app.features')));