Search code examples
dependency-injectionnestjs

Unable to inject service from global module into guard in NestJs


I am trying to protect routes using signed urls. I am using the nestjs-url-generator library for this. However it seems this library is no longer being maintained, so I forked the code instead so I can maintain it myself.

It uses a single module (UrlGeneratorModule) to handle all the logic of creating and verifying signed urls and provides a SignedUrlGuard for protecting routes. This guard gets the UrlGeneratorService injected so it can verify signatures. The issues is that NestJs is unable to resolve this dependency.

The module is globally registered in the AppModule, so it should be available. I inject the same UrlGeneratorService in my own services where it works fine, it is only unable to resolve this dependency for the guard.

I have created a minimal reproduction for further clarification.

I tried using the SignedUrlGuard in the AppController where it worked fine. So I thought there may be a problem with the module being registered globally. So I tried unmarking the UrlGeneratorModule as global and using it directly in the testModule, but the result was the same.

In my actual code I use the UrlGeneratorService in both the SignedUrlGuard (in my CustomController) and in my CustomService (registerd both in the CustomModule). The error is the same, however if I remove the dependency from the CustomService, all of a sudden it is able to resolve the dependency for the SignedUrlGuard. But I have not been able to reproduce this behavior in the minimal reproduction.

I have found a similar question here, but they never found an answer. In case it is the same issue, I hope that a reproduction can help clarify the issue.

If I can provide anything else, let me know. I feel like I miss some understanding on how NestJs does dependency injection, so any help is very much appreciated.


Solution

  • The error I'm seeing after cloning your repo is the following:

    Nest can't resolve dependencies of the SignedUrlGuard (?). Please make sure that the argument dependency at index [0] is available in the TestModule context.
    
    Potential solutions:
    - Is TestModule a valid NestJS module?
    - If dependency is a provider, is it part of the current TestModule?
    - If dependency is exported from a separate @Module, is that module imported within TestModule?
      @Module({
        imports: [ /* the Module containing dependency */ ]
      })
    

    The fact that the provider is being referred to as dependency leads me to believe that this is a circular file import issue. Looking at the file imports, the signedUrlGuard.ts file imports the urlGenerator.service.ts file which imports the helpers.ts file which imports the signedUrlGuard.ts, confirming that there is a circular file import going on here. This can lead to circular dependency-like errors as the NestJS docs mention, and should be resolved by breaking the import chain.

    Rather than forcefully reading metadata and creating this file chain, you could probably add some property to the request object to check if it has been through this custom guard, and if so then allow for the url to be signed. Sure, other devs would be able to mimic the behavior of the guard without the guard being used, but it's an idea. That, or just remove the guard from the helpers and hard code the class name for the metadata array check.