Pass parameter into route guard

后端 未结 3 1832
暖寄归人
暖寄归人 2020-12-02 12:28

I\'m working on an app that has a lot of roles that I need to use guards to block nav to parts of the app based on those roles. I realize I can create individual guard class

相关标签:
3条回答
  • 2020-12-02 13:13

    Instead of using forRole(), you can do this:

    { 
       path: 'super-user-stuff', 
       component: SuperUserStuffComponent,
       canActivate: RoleGuard,
       data: {roles: ['SuperAdmin', ...]}
    }
    

    and use this in your RoleGuard

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot)
        : Observable<boolean> | Promise<boolean> | boolean  {
    
        let roles = route.data.roles as Array<string>;
        ...
    }
    
    0 讨论(0)
  • 2020-12-02 13:19

    Here's my take on this and a possible solution for the missing provider issue.

    In my case, we have a guard that takes a permission or list of permissions as parameter, but it's the same thing has having a role.

    We have a class for dealing with auth guards with or without permission:

    @Injectable()
    export class AuthGuardService implements CanActivate {
    
        checkUserLoggedIn() { ... }
    

    This deals with checking user active session, etc.

    It also contains a method used to obtain a custom permission guard, which is actually depending on the AuthGuardService itself

    static forPermissions(permissions: string | string[]) {
        @Injectable()
        class AuthGuardServiceWithPermissions {
          constructor(private authGuardService: AuthGuardService) { } // uses the parent class instance actually, but could in theory take any other deps
    
          canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
            // checks typical activation (auth) + custom permissions
            return this.authGuardService.canActivate(route, state) && this.checkPermissions();
          }
    
          checkPermissions() {
            const user = ... // get the current user
            // checks the given permissions with the current user 
            return user.hasPermissions(permissions);
          }
        }
    
        AuthGuardService.guards.push(AuthGuardServiceWithPermissions);
        return AuthGuardServiceWithPermissions;
      }
    

    This allows us to use the method to register some custom guards based on permissions parameter in our routing module:

    ....
    { path: 'something', 
      component: SomeComponent, 
      canActivate: [ AuthGuardService.forPermissions('permission1', 'permission2') ] },
    

    The interesting part of forPermission is AuthGuardService.guards.push - this basically makes sure that any time forPermissions is called to obtain a custom guard class it will also store it in this array. This is also static on the main class:

    public static guards = [ ]; 
    

    Then we can use this array to register all guards - this is ok as long as we make sure that by the time the app module registers these providers, the routes had been defined and all the guard classes had been created (e.g. check import order and keep these providers as low as possible in the list - having a routing module helps):

    providers: [
        // ...
        AuthGuardService,
        ...AuthGuardService.guards,
    ]
    

    Hope this helps.

    0 讨论(0)
  • 2020-12-02 13:19

    @AluanHaddad's solution is giving "no provider" error. Here is a fix for that (it feels dirty, but I lack the skills to make a better one).

    Conceptually, I register, as a provider, each dynamically generated class created by roleGuard.

    So for every role checked:

    canActivate: [roleGuard('foo')]
    

    you should have:

    providers: [roleGuard('foo')]
    

    However, @AluanHaddad's solution as-is will generate new class for each call to roleGuard, even if roles parameter is the same. Using lodash.memoize it looks like this:

    export var roleGuard = _.memoize(function forRole(...roles: string[]): Type<CanActivate> {
        return class AuthGuard implements CanActivate {
            canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
                Observable<boolean>
                | Promise<boolean>
                | boolean {
                console.log(`checking access for ${roles.join(', ')}.`);
                return true;
            }
        }
    });
    

    Note, each combination of roles generates a new class, so you need to register as a provider every combination of roles. I.e. if you have:

    canActivate: [roleGuard('foo')] and canActivate: [roleGuard('foo', 'bar')] you will have to register both: providers[roleGuard('foo'), roleGuard('foo', 'bar')]

    A better solution would be to register providers automatically in a global providers collection inside roleGuard, but as I said, I lack the skills to implement that.

    0 讨论(0)
提交回复
热议问题