I have KeysModule, which can be used to add or remove API keys. I need these keys to protect some routes from unauthorized access.
To protect these routes I have created ApiGuard:
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common';
#Injectable()
export class ApiGuard implements CanActivate {
async canActivate(
context: ExecutionContext,
): Promise<boolean> {
const request = context.switchToHttp().getRequest();
return request.headers.api_key;
}
}
And then I use it in route:
#Get('/protected')
#UseGuards(ApiGuard)
async protected(#Headers() headers: Api) {
const key = await this.ks.findKey({ key: headers.api_key });
if (!key || !key.active) return 'Invalid Key';
return 'Your API key works';
}
Where ks is KeyService used to check if key is correct or not.
This solution works, but is stupid. I have to copy and paste some lines of code everywhere I want to use this guard (I mean lines in route).
I have tried to to move all logic to ApiGuard, but there I have got error, that KeyService cannot be injected to ApiGuard class. To explain, I have KeyService in providers in KeysModule, but ApiGuard is globally used.
Do you have any idea how to do it?
As of NestJS v8 it seems injecting the service as answered by zsoca in the accepted answer doesn't work anymore.
The working solution for NestJS 8 is by providing a class reference instead of a string:
constructor(#Inject(KeyService) private keyService: KeyService) {}
To inject service in guard.You can create a global module.
// ApiModule
import {Module,Global} from '#nestjs/common';
import {KeyService} from '../';
#Global()
#Module({
providers: [ KeyService ],
exports: [KeyService]
})
export class ApiModule {}
Then inject service into guard like this
// guard
export class ApiGuard implements CanActivate {
constructor(#Inject('KeyService') private readonly KeyService) {}
}
async canActivate(context: ExecutionContext) {
// your code
throw new ForbiddenException();
}
Now the problem can be solved.But I have another problem.I want to inject something into service but got this error:
Nest can't resolve dependencies of the AuthGuard (?, +). Please make sure that the argument at index [0] is available in the current context.
And here is my solution:
To Inject other dependency in KeyService,like nestjs docs say.
global guards registered from outside of any module (with useGlobalGuards() as in the example above) cannot inject dependencies since this is done outside the context of any module.
This is their sample:
// app.module.js
import { Module } from '#nestjs/common';
import { APP_GUARD } from '#nestjs/core';
#Module({
providers: [
{
provide: APP_GUARD,
useClass: RolesGuard,
},
],
})
export class ApplicationModule {}
It worked.Now I can use guard global without dependency error.
Maybe it's too late, but I ran the same issue and find a solution. Maybe there is a better one, but it's working properly for me:
Define KeysModule as a global module, you can check how to do it in nestjs docs: https://docs.nestjs.com/modules
After You can do this:
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common';
#Injectable()
export class ApiGuard implements CanActivate {
constructor(
#Inject('KeyService')
private readonly ks
) {}
const key = await this.ks.findKey();
"YOUR_CODE_HERE..."
}
Hope it's help to you or somebody who will stuck with this in the future.
You can inject a service in a guard like in any object annotated with Injectable.
If your ApiGuard need KeyService you have two choices:
Add ApiGuard in a module which import KeysModule. Then import the created module to use ApiGuard globally
Add ApiGuard in KeysModule and export it.
Services used in the guard must be exported in its module. Providing service is not enough!
Let me clarify some points here! Nestjs Guard layer was designed to let you interpose processing logic at exactly the right point in the request/response cycle and this was treated as a normal instance in Nestjs DI Container.
So what does it means when I said this was treated as a normal instance in Nestjs DI Container -> So whenever it has dependencies so that wherever it is used you need to also inject it own dependencies to where you are using. Let me give you some example:
You first have a module corresponding to handle tokens as you mentioned ^^.
#Module({
imports: [CacheModule.register()],
providers: [
TokenService,
],
exports: [TokenService],
})
export class TokenModule {}
And a Guard use it to do something ...
class YourCustomAuthGuard implements CanActive {
constructor(private readonly tokenService: TokenService) {}
canActive(executionCtx: ExecutionContext) {
this.tokenService.getSth() // ...
}
}
And you have some modules like Post or Category and there are some routing needs to verify your token existed by using guard.
#Controller('posts')
class PostController {
#UseGuards(YourCustomAuthGuard)
#Get()
getSelfPosts() {}
}
#Controller('categories')
class CategoryController {
#UseGuards(YourCustomAuthGuard)
#Get()
getPermittedCategories() {}
}
The thing here you want to use YourCustomAuthGuard as a special dependency in Post and Category module which we don't need to declare in Module as Nestjs do understand and treat Guard as a special dependency for handling execution context. So the case here is your Guard depended on TokenModule then wherever you use your Guard you need to inject TokenModule to follow with YourCustomGuard. Below is the example of PostModule and CategoryModule:
#Module({
imports: [TokenModule, TypeOrmModule.feature([Post])], // <-- Take a look at TokeModule that I have been declared
controllers: [PostController],
providers: [PostService],
exports: [PostService],
})
class PostModule {}
#Module({
imports: [TokenModule, TypeOrmModule.feature([Category], // <-- Take a look at TokeModule that I have been declared
controllers: [CategoryController],
providers: [CategoryService],
exports: [CategoryService],
})
class CategoryModule {}
So that this way can resolve your problem as your guard is used independently and does not belong to the global scope.
So what if you need to use it in your global scope? In my opinion there are two ways:
Firstly, your dependencies also belong to a global module. Let me give out some examples:
// This is the root module
#Module({
imports: [TokenModule], // <- TokenModule you declared as a global module
providers: [
{
provide: APP_GUARD,
useClass: YourCustomAuthGuard,
},
]
})
class AppModule {}
Second way is to construct directly when declaring a global guard (This way is not recommended)
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
{
logger: true,
});
const tokenService = app.get(TokenService);
app.useGlobalGuards(new YourCustomAuthGuard(tokenService));
After all, we may have some ways to resolve dependencies in nestjs guard but there is one thing that belongs to passport and nestjs design I want to present here because it may help you to have a better view to loosely coupling between nest guard and validation strategy separately and we no need to inject dependencies anywhere else. Before continuing reading the below topic, I recommend viewers that read and try with nestjs authentication before!
There are maybe some devs may used and read about nestjs authentication before and maybe also curious about how PassportStrategy and PassportGuard interact with each other and also we can easily inject things into PassportStrategy, right?
I won't go deep down into passport and nestjs implementation but I will explain about the concept then you guys can have an overview about this one.
Firstly, keep in my that guard layer still be triggered as normal if we use it in protecting any route and then we go next to the triggering strategy! But wait, we don't see any relationship between our Guard and Strategy so how can it work? I will give you an example of how I implemented a module using passport!
#Module({
imports: [
UserModule,
JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
secret: configService.get<string>('JWT_SECRET'),
}),
inject: [ConfigService],
}),
],
controllers: [AuthController],
providers: [
JwtStrategy, // <- We provide JwtStrategy as a provider here
{
provide: AuthServiceToken,
useClass: AuthServiceImpl,
},
],
})
export class AuthModule {}
Be aware that JwtStrategy extends from PassportStrategy and our guard also extends from AuthGuard of nestjs/passport.
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(configService: ConfigService) {
const secret = configService.get<string>('JWT_SECRET');
assert(secret, 'JWT_SECRET is missing');
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: secret,
});
}
validate(payload: JwtPayload): JwtPayload {
return payload;
}
}
Actually, behind scenes, passport is a separate instance out of the box of nestjs DI Container so the author of nestjs kamil created #nestjs/passport as an adapter to this library.
Firstly, let's focus on our JwtAuthGuard, we extend from AuthGuard('jwt') which is a guard factory actually and it use 'jwt' to identify strategy to be used next so that after finishing guard validation, it can make a round trip to strategy in next section under the hood.
And next, pay attention on JwtStrategy, this is where the cool part happen, this one would be initialized as a normal provider in the nestjs container so that everything can easily inject to this one and after nest injected dependencies into this one, nestjs will pass this instance reference to Passport so that can be auto triggering under the hood after AuthGuard finish their job at runtime! Read more about nestjs passport strategy and focus on passportInstance.use and spend time you would have a nice idea to use stragy out of the box with Guard in nestjs. Good luck ^^
Another approach without making the service global (so no #Global, and no #Inject) worked for me (on Nest 9)
File Structure
├── app
│ └── app.module.ts # main application module
├── status
│ ├── status.module.ts
│ └── status.service.ts # status service (which is to be injected)
├── hodor
│ ├── hodor.decorators.ts # avoid using #UseGuards(HodorGuard) => use #NotDangerousNow() instead
│ └── hodor.guard.ts # the guard using StatusService
└── door
├── door.module.ts
└── door.controller.ts # the controller guarded by #NoDanger()
AppModule
import { Module } from '#nestjs/common'
import { APP_GUARD } from '#nestjs/core'
import { DoorModule } from '../door/door.module'
import { HodorGuard } from '../hodor/hodor.guard'
import { StatusModule } from '../status/status.module'
#Module({
imports: [
HodorModule,
DoorModule,
StatusModule
],
providers: [
{ provide: APP_GUARD, useClass: HodorGuard } // makes the guard used everywhere
]
})
export class AppModule {}
StatusModule
import { Module } from '#nestjs/common'
import { StatusService } from './status.service'
#Module({
providers: [StatusService],
exports: [StatusService]
})
export class StatusModule {}
StatusService
import { Injectable } from '#nestjs/common'
#Injectable()
export class StatusService {
/**
* Returns whether Hodor thinks it's dangerous at the given time.
* Just async because in general there is a db call there.
*/
async isDangerousAt(date: Date): Promise<boolean> {
return date.getHours() > 22
}
}
hodor.decorators.ts
import { SetMetadata } from '#nestjs/common'
export const DANGER_KEY = 'danger'
// 403 if Hodor thinks it's dangerous at the given date/time
// usage: #NotDangerous(date) => that's right I see no possible usage for that one, it's for the example
export const NotDangerous = (date: Date) => SetMetadata(DANGER_KEY, { date })
// 403 if Hodor thinks it's dangerous right now
// usage: #NotDangerousNow()
export const NotDangerousNow = () => SetMetadata(DANGER_KEY, { date: new Date() })
hodor.guard.ts
import { Injectable, CanActivate, ForbiddenException } from '#nestjs/common'
import { Reflector } from '#nestjs/core'
import { DANGER_KEY } from './hodor.decorator'
import { StatusService } from '../status/status.service'
type HodorMetadata = {
status: PlatformHodor
expected: boolean
}
#Injectable()
export class HodorGuard implements CanActivate {
constructor(
private reflector: Reflector,
private readonly statusService: StatusService // Do not use #Inject (or nest won't be able to inject it)
) {}
/**
* Rely on status service to check if Hodor thinks it is dangerous at the given date/time.
* #throws ForbiddenException if Hodor thinks it's dangerous at the given date/time => 403
*/
async canActivate(context: any): Promise<boolean> {
// METADATA DANGER_KEY is the magic link between NotDangerous decorator and the guard
const metadata = this.reflector.getAllAndOverride<HodorMetadata>(
DANGER_KEY,
[context.getHandler(), context.getClass()]
)
// because we inject the guard in the whole app
// => it must let pass in routes with no decorator
if (!metadata) return true
// 403 if dangerous
const isDangerous = await this.statusService.isDangerousAt(metadata.date)
if (isDangerous) {
throw new ForbiddenException(`Hodor thinks it's dangerous on ${metadata.date}`)
}
// let pass otherwise
return true
}
}
DoorModule
import { Module } from '#nestjs/common'
import { DoorController } from './door.controller'
#Module({
controllers: [DoorController],
providers: [DoorService],
})
export class DoorModule {}
DoorService
import { Controller, HttpCode, Post } from '#nestjs/common'
import { NotDangerousNow } from '../hodor/hodor.decorator'
#Controller('door')
export class DoorController {
#NotDangerousNow()
#Post()
open(): Promise<string> {
return 'please, come in'
}
}
Related
I have module which implement JWT passport
const appGuard = {
provide: APP_GUARD,
useClass: JwtAuthGuard,
};
#Module({})
export class JwtAuthModule {
static forRoot(options: { jwks: string, excludePath: string[] }): DynamicModule {
const exportableProviders = [
{ provide: JWKS_URI, useValue: options.jwks },
{ provide: EXCLUDE_FROM_AUTH, useValue: options.excludePath },
appGuard,
];
return {
module: JwtAuthModule,
providers: [
...exportableProviders,
JwtStrategy,
JwtAuthGuard
],
exports: exportableProviders,
};
}
}
But I am getting an error:
Error: Nest cannot export a provider/module that is not a part of the
currently processed module (JwtAuthModule). Please verify whether the
exported APP_GUARD is available in this particular context.
Possible Solutions:
Is APP_GUARD part of the relevant providers/imports within JwtAuthModule?
What is interesting if I debug i see that APP_GUARD has name in providers like: APP_GUARD (UUID: 63cf5b8e-815e-4034-a4a8-c6196d98b612)
Simple work around is to register APP_GUARD in providers at each root module and import module, but i would love to be able just to import this module once if possible...
APP_GUARD, along with the other APP_* providers, are special provider tokens that Nest allows to be bound multiple times. As soon as it is used it is bound as a global enhancer of whatever type relating back to the token (pipe, guard, interceptor, filter). As it's bound, there's no need to export the enhancer as well, there's nothing else that can make use of it directly, it's bound globally so it's already fine as is. Just remove the APP_GUARD provider you have from the exports and you should be good to go
I have seen a couple of articles related to this error here, but I still need some guidance on exactly what is going on because I have tried to make HttpService a part of the current PublicKeysModule and i have tried to import it within the PublicKeysModule.
This is my current public keys module:
import { Module } from "#nestjs/common";
import { PublicKeysService } from "./public-keys.service";
import { PublicKeysController } from "./public-keys.controller";
#Module({
providers: [PublicKeysService],
controllers: [PublicKeysController],
})
export class PublicKeysModule {}
The HttpService is coming from Axios and it's imported in the public keys service:
import { Injectable } from "#nestjs/common";
import { HttpService } from "#nestjs/axios";
import { ConfigService } from "#nestjs/config";
import { MessageResponseDto } from "../../../shared-dtos";
import { ConfigService } from "./dtos/public-key-response.dto";
#Injectable()
export class PublicKeysService {
constructor(
private _httpService: HttpService,
private _config: ConfigService
) {}
}
I tried adding HttpModule as imports: [] in the public keys module as I learned in another post that it should be HttpModule and not HttpService, but when I did that, the error did not go away.
Now when I used HttpService in the imports: [HttpService] for public key module. I get a totally new error saying:
Please make sure that the argument AXIOS_INSTANCE_TOKEN at index [0]
is available in the HttpService context
So I am stumped here.
You're missing the imports: [HttpModule] in your PublicKeysModule. That's what gives access to the HttpService in the context of the module.
You need to import the HTTPModule in your PublicKeyModule imports array.
import { HttpModule } from "#nestjs/axios";
import { Module } from "#nestjs/common";
import { PublicKeysService } from "./public-keys.service";
import { PublicKeysController } from "./public-keys.controller";
#Module({
imports: [HttpModule],
providers: [PublicKeysService],
controllers: [PublicKeysController],
})
export class PublicKeysModule {}
Here is a detailed article about how the HTTP Module works How to use Axios in NestJs
I'm new in Nestjs, and I don't understand when I need to import the whole module or only the service if I want to Inject in another module.
For example:
I have my loggingModule
import { Module } from "#nestjs/common";
import { LoggingService } from "./logging.service";
#Module({
providers: [LoggingService],
exports: [LoggingService],
})
export class LoggingModule {}
with my logginService:
import { Injectable } from "#nestjs/common";
#Injectable()
export class LoggingService {
logToConsole(logString: string) {
console.log(logString)
}
}
I want to import it into another module, like BookModule
import { Module } from "#nestjs/common";
import { LoggingModule } from "src/logging/logging.module";
import { BookController } from "./book.controller";
import { BooksService } from "./books.service";
#Module({
controllers: [BookController],
providers: [BooksService],
imports: [LoggingModule]
})
export class BooksModule {
}
and inside my controller I can do:
#Controller('books')
export class BookController {
constructor(private booksService: BooksService, private loggingService: LoggingService) {}
the question is:
When I need to import the whole module instead of the single service (LogginService) in providers, like:
#Module({
controllers: [BookController],
providers: [BooksService,LoggingService],
})
export class BooksModule {
99% of the time, import the module. In Nest, if you import the module, you get the same instance of the already created class. If you add the provider to the providers array, you end up getting a new instance, which is not something many people usually want. So long as the module exports the provider, you'll re-use the created instance, which means less memory usage, and less leaks of the number of class instances, which means less time tracking down why this.someClass.field doesn't equal this.someClass.field
I have a question regarding dynamic modules importing.
Let's say I have #Injectable service which depends on the parameter that came from the configuration:
#Injectable()
export class StatsService {
constructor(#Inject(SERVICE_NAME) private serviceName: string) {}
...
}
Let's say I expose this service through the Dynamic module:
#Module({})
export class CoreServicesModule {
static register(coreConfig: CoreServicesConfig): DynamicModule {
const { serviceName } = coreConfig;
return {
module: CoreServicesModule,
providers: [
{
provide: SERVICE_NAME,
useValue: serviceName
},
StatsService
],
exports: [
StatsService
]
};
}
}
Let's say my application is pretty big, and I have a lot of different modules.
But I need StatsService in every one of them.
So for example to be able to use StatsService in one of the modules i need to do something like:
#Module({
imports: [CoreServicesModule.register({ serviceName: 'test', ... })]
})
export class SomeModule { ... }
And I need to do it for each module...
Do we have a way to do it only once in NestJS?
Or how we can re-use the already registered module?
#Global decorator also not help me here cause I still need to import the module in each place...
#Global() decorator will help you if you are interested in using StatService in all other modules without the need to import that module!
BUT from your code it seems that each module that import CoreServicesModule would also make a small adjustment to it by dynamically specifying the providers it has (such as ValidationPipe).
in that case you should NOT make it #Global() since global modules should be registered only once
I'm currently working through the database integration docs for NestJS using TypeOrm. In these docs there are examples that show how to inject a custom database repository using the app.module from NestJS. All of these examples inject classes using the actual type of the custom repository.
#Injectable()
export class AuthorService {
constructor(private authorRepository: AuthorRepository) {}
}
This code is injected via the app.modules by providing a import like such:
#Module({
imports: [TypeOrmModule.forFeature([AuthorRepository])],
controller: [AuthorController],
providers: [AuthorService],
})
export class AuthorModule {}
This works well if you are fine with programming against an implementation, but I prefer to use an interface in my classes. I've already found the solution to injecting classes via an interface with NestJS in a previous question, but when I try to inject my custom repository like that, it doesn't seem to instanciate correctly and becomes undefined.
(node:16658) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'save' of undefined
Because of this, I assume you can only inject customRepositories via the forFeature() call in the app.module, but that won't allow me to use interfaces for injection, as far as I know. Is there any other way I can inject a custom TypeOrm repository without having the replace all my interfaces for the implementation of my custom repository? Thanks in advance!
Edit
Here is my current code, I managed to get it to inject, but this still forces me to use the implementation instead of the interface each time I call the constructor. This is mainly an issue when testing due to mocking.
#CommandHandler(FooCommand)
export class FooHandler
implements ICommandHandler<FooCommand> {
private fooRepository: IFooRepository; // Using Interface as a private property.
private barEventBus: IEventBus;
constructor(fooRepository: FooRepository,
barEventBus: EventBus) { // Forced to use implementation in constructor for injection.
this.fooRepository = fooRepository;
this.barEventBus = barEventBus;
}
#EntityRepository(FooEntity)
export class FooRepository extends Repository<FooEntity> implements IFooRepository {
getFoo() {
// Do stuff
}
}
#Module({
imports: [TypeOrmModule.forRoot(), TypeOrmModule.forFeature([FooRepository]],
// Other module setup
})
export class AppModule {}
It should work with using the InjectRepository decorator where you specify the Repository but then you type is as your interface instead and when testing you just provide the IFooRepository!
Example code:
constructor(#InjectRepository(FooRepository) fooRepository: IFooRepository,
barEventBus: EventBus) {
Edit: This answer is crap, that abstract-class-as-interface hack used does not work out as the defined methods seem to be optional to implement despite being marked as abstract.
Well, kind of got it working. Based on this answer https://stackoverflow.com/a/74561702/913136 I used an abstract class as interface (you can actually implement it) for not being required to pass strings around as tokens. Only drawback is the misuse of the abstract class. Not sure yet if I like it.
Using an actual interface in the same way seems not to be possible unfortunately. Urgh.
#Module({
imports: [
TypeOrmModule.forRoot({
...dataSource.options,
autoLoadEntities: true,
}),
TypeOrmModule.forFeature([Listing]),
],
controllers: [ViewListingController],
providers: [
{
provide: ListingRepository,
useClass: TypeOrmListingRepository,
},
],
})
makeshift interface:
import { Listing } from "./Listing";
export abstract class ListingRepository {
abstract findMostRecent: () => Promise<Listing[]>;
}
implementation:
import { Listing, ListingRepository } from "../../Domain";
import { Injectable } from "#nestjs/common";
import { Repository, DataSource } from "typeorm";
#Injectable()
export class TypeOrmListingRepository
extends Repository<Listing>
implements ListingRepository
{
constructor(private dataSource: DataSource) {
super(Listing, dataSource.createEntityManager());
}
findMostRecent() {
return this.find({});
}
}
import { Controller, Get } from "#nestjs/common";
import { ListingRepository } from "../Domain";
#Controller("listings")
export class ViewListingController {
constructor(private readonly listingRepo: ListingRepository) {}
#Get("/most-recent")
listMostRecent() {
return this.listingRepo.findMostRecent();
}
}