graphql
graphql copied to clipboard
GraphQL request is not triggering the global guard
Is there an existing issue for this?
- [X] I have searched the existing issues
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?
- [X] macOS
- [ ] Windows
- [ ] Linux
Other
I discussed the issue with one of the NestJS core team member and here is our discussion on Discord
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",