Load permissions before loading module when is accesed final route into url

Viewed 1064

Ok, I have a route: site/dashboards/orders

  {
    path: 'dashboards',
    loadChildren: () => import('./dashboards/dashboards.module').then(m => m.DashboardsModule),
    canActivate: [AuthGuard, NgxPermissionsGuard],
    data: {
      permissions: {
        only: ['admin', 'dashboards'],
        redirectTo: 'homepage'
      }
    }
  }

In my app component I check if user is authenticated, and if yes, get roles from api and load permissions using NgxPermissions module

app.component.ts (in constructor):

constructor(private services) {
  this.checkAuth();
}

checkAuth() {
   apiService.checkAuth().subscribe((value: boolean) => {
     if (value) {
        // get roles with another api call
          apiService.getRoles().subscribe(roles => {
            this.permissionsService.loadPermissions(roles);
          });  
     }
   });
}

I know that is not best way to have subscription inside another subscription, but this doesn't matter right now.

If I access the site from first page, the problem doesn't exist because:

  • go to site/homepage => checkAuth() is executed and have enough time to get api results => load roles

  • navigate to dashboards/orders => roles are already loaded from previous step (if I don't nagivate too faster) and when routing go to path 'dashboards' and check data with permissions ONLY ['admin', 'dashboards'], this will allow me to access the route, because the roles exist.

But, If I try to access directly site/dashboards/orders:

  • app.component.ts is first loaded component in Angular, so will call checkAuth() method. But because this is an observable, will pass to next action (checking routing data permissions) before finishing it. Because roles are not loaded the app will redirect me to 'homepage'.

How can I wait for loading roles before accessing routing and verify permissions?

I need to get roles only once (at first app accessing) and not for each routing module. Its possible to listen observables in routing-modules?

Shortly, I need to load roles before routes.

thanks

1 Answers

I found a solution, I don't know if is the best, but its working:

Renouncing at NgxPermissionsGuard and creating another guard "PermissionsGuard" which implements CanActivate and send roles and redirectTo as parameters in both guards.

Routing:

{
    path: 'dashboards',
    loadChildren: () => import('./dashboards/dashboards.module').then(m => m.DashboardsModule),
    canActivate: [AuthGuard, PermissionsGuard],
    data: {
      roles: ['admin', 'dashboards'],
      redirectTo: 'homepage'
    }
  }

PermissionsGuard.ts:

export class PermissionsGuard implements CanActivate {
  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    const routeRoles = next.data.roles as Array<string>;
    const redirectTo = next.data.redirectTo;

    const userName = localStorage.getItem('userName');
    return this.getRoles(userName, routeRoles, redirectTo);
  }

  getRoles(userName: string, routeRoles: string[], redirectTo: string) {
    let userRoles = [];
    const roles = this.permissionsService.getPermissions();
    for (const key in roles) {
      userRoles.push(key);
    }

    if (userRoles.length > 0) {
      return this.isInRole(userRoles, routeRoles, redirectTo);
    } else {
      // api call, get user roles 
      return this.isInRole(apiResult, routeRoles, redirectTo);
    }
  }

  isInRole(userRoles: string[], routeRoles: string[], redirectTo: string) {
    // let canAccessRoute = false;
    // check if routeRoles contain any element of userRoles

    if (canAccessRoute) {
      return true;
    } else {
      this.router.navigateByUrl(redirectTo);
    }
  }
}

Both guards (AuthGuard & PermissionsGuard) need to return true for letting user to access the route. Some data are result of promises, due of this is needed to use returning methods.

Related