【问题标题】:Nestjs graphql field guardNestjs graphql 场守卫
【发布时间】:2020-05-29 13:22:21
【问题描述】:

我正在尝试为 graphql 字段制作角色守卫。像这样的:

import { Field, ObjectType } from 'type-graphql';
import { Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn } from 'typeorm';
import Role from '../role/role.entity';

@ObjectType()
@Entity()
class User {
  @Field()
  @PrimaryGeneratedColumn()
  readonly id: number;


  @Field()
  @Column()
  @Guard('USER_SEE_NAME') //this line
  name: string;

  @Field()
  @Column()
  surname: string;
}

export default User;

目标是,如果用户没有所需的角色,则该字段将被发送到带有 null 值的客户端。

我发现我应该使用class-transformer,但我没有找到任何nestjs 的例子。我还研究了nestjs documentation,但只有内置装饰器的示例,它们没有在ObjectType 中使用。

我会使用Authorized 装饰器,但我需要访问nestjs 上下文来获取userId,但我还没有找到方法。

你现在有什么例子或方法吗?

【问题讨论】:

    标签: javascript nestjs graphql-js typegraphql class-transformer


    【解决方案1】:

    所以几天后我找到了解决方案。我编写了一个自定义拦截器,如下所示:

    import {
      Injectable,
      ExecutionContext,
      CallHandler,
      ClassSerializerInterceptor,
      Inject,
    } from '@nestjs/common';
    // eslint-disable-next-line import/no-extraneous-dependencies
    import { Observable } from 'rxjs';
    // eslint-disable-next-line import/no-extraneous-dependencies
    import { map } from 'rxjs/operators';
    import { GqlExecutionContext } from '@nestjs/graphql';
    import { ClassTransformOptions } from '@nestjs/common/interfaces/external/class-transform-options.interface';
    import { PlainLiteralObject } from '@nestjs/common/serializer/class-serializer.interceptor';
    import { CLASS_SERIALIZER_OPTIONS } from '@nestjs/common/serializer/class-serializer.constants';
    import { loadPackage } from '@nestjs/common/utils/load-package.util';
    import AuthService from './auth.service';
    
    const REFLECTOR = 'Reflector';
    
    let classTransformer: any = {};
    
    @Injectable()
    class ResourceInterceptor extends ClassSerializerInterceptor {
      constructor(
        @Inject(AuthService) private authService: AuthService,
        @Inject(REFLECTOR) protected readonly reflector: any,
      ) {
        super(reflector);
        classTransformer = loadPackage('class-transformer', 'ClassSerializerInterceptor', () =>
          // eslint-disable-next-line global-require
          require('class-transformer'),
        );
        // eslint-disable-next-line global-require
        require('class-transformer');
      }
    
      serializeCustom(
        response: PlainLiteralObject | Array<PlainLiteralObject>,
        options: ClassTransformOptions,
        user: number,
      ): PlainLiteralObject | PlainLiteralObject[] {
        const isArray = Array.isArray(response);
        if (!(typeof response === 'object') && response !== null && !isArray) {
          return response;
        }
        return isArray
          ? (response as PlainLiteralObject[]).map(item => this.transformToClass(item, options))
          : this.transformToGuard(this.transformToClass(response, options), user);
      }
    
      transformToClass(plainOrClass: any, options: ClassTransformOptions): PlainLiteralObject {
        return plainOrClass && plainOrClass.constructor !== Object
          ? classTransformer.classToClass(plainOrClass, options)
          : plainOrClass;
      }
    
      intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
        const options = this.getContextOptionsCustom(context);
        const ctx = GqlExecutionContext.create(context);
        const { user } = ctx.getContext().req;
        return next.handle().pipe(
          map((res: PlainLiteralObject | Array<PlainLiteralObject>) => {
            return this.serializeCustom(res, options, user);
          }),
        );
      }
    
      private getContextOptionsCustom(context: ExecutionContext): ClassTransformOptions | undefined {
        return (
          this.reflectSerializeMetadataCustom(context.getHandler()) ||
          this.reflectSerializeMetadataCustom(context.getClass())
        );
      }
    
      private reflectSerializeMetadataCustom(
        obj: object | Function,
      ): ClassTransformOptions | undefined {
        return this.reflector.get(CLASS_SERIALIZER_OPTIONS, obj);
      }
    
      async transformToGuard(response, userId: number) {
        // eslint-disable-next-line no-restricted-syntax
        for (const key of Object.keys(response)) {
          const item = response[key];
          // eslint-disable-next-line no-underscore-dangle
          if (typeof item === 'object' && item !== null && item.__RESOURCE_GUARD__ === true) {
            // eslint-disable-next-line no-await-in-loop
            response[key] = (await this.authService.hasAccess(userId, item.resources))
              ? response[key].value
              : null;
          }
        }
        return response;
      }
    }
    
    export default ResourceInterceptor;
    

    用法:

    @UseInterceptors(ResourceInterceptor)
    async userGetLogged(@CurrentUser() userId: number) {
      return this.userService.findById(userId);
    }
    

    【讨论】:

      猜你喜欢
      • 2022-08-11
      • 2021-10-22
      • 2023-03-16
      • 2021-04-03
      • 2019-09-14
      • 2020-11-20
      • 1970-01-01
      • 2020-12-14
      • 2017-11-27
      相关资源
      最近更新 更多