nestjs / graphql

GraphQL (TypeScript) module for Nest framework (node.js) 🍷
https://docs.nestjs.com/graphql/quick-start
MIT License
1.45k stars 393 forks source link

GraphQL request is not triggering the global guard #3024

Open ErangaHeshan opened 1 year ago

ErangaHeshan commented 1 year ago

Is there an existing issue for this?

Current behavior

I have a user role guard that needs to access a TypeORM repository inside. I set it inside my AppModule as a global guard. However, I see that when a GraphQL request comes, it does not go through the guard but simply hits my GraphQL query. Any idea what is going wrong here?

AppModule

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      autoSchemaFile: 'src/schema.gql',
      context: ({ req }) => ({
        req,
        user: new JwtService().decode(
          (req.headers.authorization as string).replace('Bearer ', ''),
        ),
      }),
    }),
    // I need to make `TypeOrmModule` asynchronous because not all the requests
    // will need a database connection, and the requests that need will carry
    // the information to initialize the database connection inside the
    // authorization header as a JWT.
    //
    // e.g.: Check the following payload inside the JWT.
    //
    // ```json
    // {
    //   "id": "2",
    //   "database": "test",
    //   "iat": 1696519539,
    //   "exp": 1696526739
    // }
    // ```
    //
    TypeOrmModule.forRootAsync({
      useClass: TypeOrmConfigService,
      dataSourceFactory: async (options) =>
        await TypeOrmConfigService.getOrCreateDataSource(options),
    }),
    UserDetailsModule,
  ],
  providers: [
    AppResolver,
    {
      provide: APP_GUARD,
      // I'm using the factory method here since the repository needs to change based
      // on the customer. We have a separate database for each customer. If I use
      // `useClass: UserRightGuard` syntax here, I get `undefined` for `reflector`.
      useFactory: (reflector, repo) => new UserRightGuard(reflector, repo),
      inject: [Reflector, getRepositoryToken(UserDetails)],
      // If I make this request scoped, the guard will execute but that is not
      // the solution I want since not all the requests (e.g.: login) will have
      // user info in the request header.
      //
      // scope: Scope.REQUEST,
    },
  ],
})
export class AppModule {}

UserRightGuard

@Injectable()
export class UserRightGuard implements CanActivate {
  constructor(
    private reflector: Reflector,
    private userDetailsRepo: Repository<UserDetails>,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    try {
      const requiredRights = this.reflector.getAllAndOverride(RequireRights, [
        context.getHandler(),
        context.getClass(),
      ]);
      // Returns `true` if there is no right specified as required.
      if (!requiredRights) return true;

      const ctx = GqlExecutionContext.create(context).getContext();
      const { user } = ctx;
      const { right } = await this.userDetailsRepo.findOne({
        where: { id: user.id },
      });
      return requiredRights.some((r) => r == right);
    } catch (err) {
      return false;
    }
  }
}

UserDetailsResolver

@Resolver(() => UserDetails)
export class UserDetailsResolver {
  constructor(
    @InjectRepository(UserDetails)
    private userDetailsRepository: Repository<UserDetails>,
  ) {}

  @RequireRights(['subscription'])
  @Query(() => [UserDetails])
  public async userDetails(): Promise<UserDetails[]> {
    return await this.userDetailsRepository.find();
  }

  @Query(() => UserDetails)
  public async firstUserDetail(): Promise<UserDetails> {
    const userDetails = await this.userDetailsRepository.find();
    return userDetails[0];
  }
}

Minimum reproduction code

https://github.com/ErangaHeshan/nests-issues-trigger-global-guard#2-a-user-without-proper-access

Steps to reproduce

No response

Expected behavior

I would expect the GraphQL queries annotated using @RequireRights(['subscription']) to invoke the guard and check if the user who made the request has appropriate right value in their user_details table. If the user does not have proper right, the query should return FORBIDDEN error inside its response body.

Package version

12.0.8

Graphql version

graphql: 16.8.1 @apollo/server: 4.9.4 @nestjs/platform-express: 10.0.0 @nestjs/typeorm: 10.0.0

NestJS version

10.0.0

Node.js version

16.13.2

In which operating systems have you tested?

Other

I discussed the issue with one of the NestJS core team member and here is our discussion on Discord

lucassith commented 8 months ago

I'm experiencing the same problem. I have registered GraphQLModule.forRootAsync and I also have simple controller with single /health GET request.

My guard is simple, just logging on canActivate. I set it as global guard with: app.useGlobalGuards(new TestGuard());

GraphQL routes simply ignore the guard meanwhile /health request prints logs.

"@nestjs/apollo": "^12.0.11", "@neo4j/graphql": "^4.4.5", "@apollo/server": "^4.9.4", "@nestjs/core": "^10.3.1", "@nestjs/graphql": "^12.0.9",