'Nest JS authorization with CASL doesn't work as expected

EXPECTING:

  • Be able to get user info with id equal to my id only (which is saved in JWT token).

CURRENT RESULT:

  • I am able to get info about all users with some id.

Used Nest Js docs while creating this solution. Do appreciate your help.

  1. /casl-ability.factory.ts
type Subjects = InferSubjects<typeof User | typeof Role | 'User'> | 'all';
export type AppAbility = Ability<[Action, Subjects]>;

export class CaslAbilityFactory {
  createForUser(userDataFromJWT: JwtAccessTokenInput) {
    const { can, cannot, build } = new AbilityBuilder<
      Ability<[Action, Subjects]>
    >(Ability as AbilityClass<AppAbility>);

    // TESTING THIS CASE
    can(Action.Read, User, {
      id: userDataFromJWT.sub,
    });

    return build({
      detectSubjectType: (item) =>
        item.constructor as ExtractSubjectType<Subjects>,
    });
  }

  private hasRole(roles: unknown[], role: UserRoles): boolean {
    return roles.includes(role);
  }
}
  1. /getUser.policyHandler.ts
    export class GetUserPolicyHandler implements IPolicyHandler {
      handle(ability: AppAbility) {
        return ability.can(Action.Read, User);
      }
    }
  1. /types.ts
export enum Action {
  Manage = 'manage',
  Create = 'create',
  Read = 'read',
  Update = 'update',
  Delete = 'delete',
}

export interface IPolicyHandler {
  handle(ability: AppAbility): boolean;
}

type PolicyHandlerCallback = (ability: AppAbility) => boolean;

export type PolicyHandler = IPolicyHandler | PolicyHandlerCallback;
  1. /policies.guard.ts
@Injectable()
export class PoliciesGuard implements CanActivate {
  constructor(
    private reflector: Reflector,
    private caslAbilityFactory: CaslAbilityFactory,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const policyHandlers =
      this.reflector.get<PolicyHandler[]>(
        CHECK_POLICIES_KEY,
        context.getHandler(),
      ) || [];

    const ctx = GqlExecutionContext.create(context);
    const { user }: { user: JwtAccessTokenInput } = ctx.getContext().req;
    const ability = this.caslAbilityFactory.createForUser(user);

    return policyHandlers.every((handler) =>
      this.execPolicyHandler(handler, ability),
    );
  }

  private execPolicyHandler(handler: PolicyHandler, ability: AppAbility) {
    if (typeof handler === 'function') {
      return handler(ability);
    }
    return handler.handle(ability);
  }
}
  1. user.resolver.ts
@Resolver(() => User)
export class UserResolver {
  constructor(private readonly userService: UserService) {}

  @Query(() => User, { name: 'user' })
  @UseGuards(PoliciesGuard)
  @CheckPolicies(new GetUserPolicyHandler())
  @UseInterceptors(UserNotExistsByIDInterceptor)
  async findOne(@Args('id', { type: () => Int }) id: number): Promise<User> {
    return await this.userService.findOne(id);
  }
}


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source