javascript - Nestjs graphql 场守卫

标签 javascript nestjs graphql-js typegraphql class-transformer

我正在尝试为 graphql 字段创建 Angular 色防护。像这样的事情:

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;

目标是,如果用户没有所需的 Angular 色,则该字段将以 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/

相关文章:

Javascript 按周/月/年分组

reactjs - Gatsby 和 GraphQL - 从查询的文件中呈现下载链接

reactjs - Graphql & Relay 制作案例

javascript - NestJs 将 GRPC 异常转换为 HTTP 异常

javascript - 如何修复 graphql 突变类型名称错误

javascript - 如何在客户端扩大选择范围

javascript - 使用 Angular 在 MxGraph 上覆盖 graphHandlerMouseUp 时无限循环

javascript - 在 JavaScript 中向函数添加行为?

testing - Nestjs 单元测试期间可选的 DTO

Docker Compose 无法连接到数据库