我正在尝试为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,但我还没有找到一种方法。

您现在是否有一些示例或实现方法?

最佳答案

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

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);
}

关于javascript - NestJS graphql现场防护,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/60214536/

10-09 21:14