簡體   English   中英

Nestjs graphql 場衛

[英]Nestjs graphql field guard

我正在嘗試為 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 文檔,但只有內置裝飾器的示例,它們沒有在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);
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM