Nest JS: TypeError: JwtStrategy requires a secret or key? when using with injection token - node.js

I want to create an auth module as a separate node package so that we can reuse it for multiple projects.
AS part of it we want to supply the necessary config from the main app via AuthLibModule. register() which supplies this param as config.
and the passed config will be held using an injection token to use it via dependency injection.
HOW DO WE ACHIEVE IT?
AuthLib.module.ts
#Module({})
export class AuthLibModule {
static register(config: AuthLibConfig): DynamicModule {
const authConfigToken = {
provide: AuthLibConstants.JWT_CONFIG_TOKEN,
useValue: config.jwtConfig
};
return {
module: AuthLibModule,
imports: [
ConfigModule.forRoot(
{
isGlobal: true
}
),
MongooseModule.forRoot(config.mongoDbUrl ? config.mongoDbUrl : process.env.MONGO_URI),
AuthModule
],
controllers: [],
exports: [authConfigToken],
providers: [authConfigToken]
}
}
}
APP.MODULE.TS
#Module({
imports: [
AuthLib.register({
authTokenExpiration: 60S,
authTokenSecret: XYZZZZZ,
refreshTokenSecret: YYYYYYYYYYYYYYYYY,
refreshTokenExpiration: 90S
})
],
controllers: [],
providers: [],
})
export class AppModule { }
jwt.strategy.ts
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, GuardNames.jwtAuth) {
constructor(
#Inject(AuthLibConstants.JWT_CONFIG) jwtConfig: JWTConfig
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
// jwtConfig.authTokenSecret -> getting as undefined.
secretOrKey: jwtConfig.authTokenSecret
});
}
async validate(payload: TokenParsePayload): Promise<TokenParsePayload> {
return payload;
}
}
Error:
[Nest] 21492 - 28/02/2022, 5:30:03 pm ERROR [ExceptionHandler] JwtStrategy requires a secret or key
TypeError: JwtStrategy requires a secret or key
at new JwtStrategy (D:\Freelancing\code-mentor\infra-services\auth-lib\node_modules\passport-jwt\lib\strategy.js:45:15)
at new MixinStrategy (D:\Freelancing\code-mentor\infra-services\auth-lib\node_modules\#nestjs\passport\dist\passport\passport.strategy.js:32:13)
at new JwtStrategy (D:\Freelancing\code-mentor\infra-services\auth-lib\src\auth\strategies\Jwt-auth.strategy.ts:17:9)
at Injector.instantiateClass (D:\Freelancing\code-mentor\infra-services\auth-lib\node_modules\#nestjs\core\injector\injector.js:301:19)
at callback (D:\Freelancing\code-mentor\infra-services\auth-lib\node_modules\#nestjs\core\injector\injector.js:48:41)
at Injector.resolveConstructorParams (D:\Freelancing\code-mentor\infra-services\auth-lib\node_modules\#nestjs\core\injector\injector.js:124:24)
at Injector.loadInstance (D:\Freelancing\code-mentor\infra-services\auth-lib\node_modules\#nestjs\core\injector\injector.js:52:9)
at Injector.loadProvider (D:\Freelancing\code-mentor\infra-services\auth-lib\node_modules\#nestjs\core\injector\injector.js:74:9)
at async Promise.all (index 6)
at InstanceLoader.createInstancesOfProviders (D:\Freelancing\code-mentor\infra-services\auth-lib\node_modules\#nestjs\core\injector\instance-loader.js:44:9)

Related

NestJS share dynamic module instance

I'm struggling a bit with Nest's dynamic modules.
I have a utility service which needs a password to be injected on startup.
#Injectable()
export class CryptoService {
constructor(
#Inject("password") private password: string
) {}
...
}
This service is part of a "shared" CryptoModule...
#Module({
providers: [CryptoService]
,exports: [CryptoService]
})
export class CryptoModule {
public static forRoot(password: string): DynamicModule {
const providers = [{
provide: "password"
,useValue: password
}];
return {
module: CryptoModule
,providers: providers
};
}
}
With "shared" I mean that it's not specific to the current app, so it can be reused in other Nest apps as well.
I import the CryptoModule in my AppModule like so...
#Module({
imports: [
...
,CryptoModule.forRoot("super-secure-password")
,EmailModule
]
,controllers: [...]
,providers: [...]
,exports: [CryptoModule]
})
export class AppModule {
...
}
This works and I can inject the CryptoService in my AppModule classes/services/controllers.
Now I have a 3rd Module (EmailModule) which should be independent from my app, so I can reuse it in other Nest apps as well.
Question now is, how do I import the CryptoModule in the EmailModule without having to set the password in the EmailModule? It should use the password that was passed to the CryptoModule in AppModule.
I tried importing CryptoModule like so...
#Module({
imports: [
...
,CryptoModule
]
,providers: [EmailService]
,exports: [EmailService]
})
export class EmailModule {
...
}
This code however triggers an exception on startup saying:
Nest can't resolve dependencies of the CryptoService...
If password is a provider, is it part of the current CryptoModule?
Is there a best practice of how to achieve this? I could make EmailModule dynamic as well and pass in the password, so it can forward it to the CryptoModule, but somehow that doesn't feel right.
Thanks!
I ended up doing the following (probably there are better ways of doing this)...
Added a static forChildren() function to the CryptoModule, which is used to initialize the module when being imported by the EmailModule (or any other module). The forRoot() method stores the password in an Observable, which is used by forChildren() to asynchronously retrieve the password. Doing this async is required to defeat the race condition between forChildren() and forRoot().
import { firstValueFrom, ReplaySubject } from 'rxjs';
...
#Module({
providers: [CryptoService]
,exports: [CryptoService]
})
export class CryptoModule {
private static readonly credentialsResolved = new ReplaySubject<string>(1);
public static forRoot(password: string): DynamicModule {
CryptoModule.credentialsResolved.next(password);
const providers = [{
provide: "password"
,useValue: password
}];
return {
module: CryptoModule
,providers: providers
};
}
public static forChildren(): DynamicModule {
const providers = [{
provide: "password"
,useFactory: async () => await firstValueFrom(CryptoModule.credentialsResolved)
}];
return {
module: CryptoModule
,providers: providers
};
}
}
Usage in EmailModule:
#Module({
imports: [
...
,CryptoModule.forChildren()
]
,providers: [EmailService]
,exports: [EmailService]
})
export class EmailModule {
...
}

Cannot inject custom provider for dynamic module

I tried to create a dynamic module PermissionModule like the following:
permTestApp.module.ts
#Module({
imports: [PermissionModule.forRoot({ text: 'abc' })],
providers: [],
controllers: [PermissionTestController],
})
export class PermissionTestAppModule {}
permission.module.ts
import { DynamicModule, Module } from '#nestjs/common'
import { PermissionGuard } from './guard/permission.guard'
#Module({})
export class PermissionModule {
public static forRoot(config: { text: string }): DynamicModule {
return {
module: PermissionModule,
providers: [
{
provide: 'xoxo',
useValue: config.text,
},
PermissionGuard,
],
exports: [PermissionGuard],
}
}
}
permission.guard.ts
import {
CanActivate,
ExecutionContext,
Inject,
Injectable,
} from '#nestjs/common'
#Injectable()
export class PermissionGuard implements CanActivate {
constructor(#Inject('xoxo') private readonly config: string) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
console.log(31, this.config)
return true
}
}
AFAIK, 'abc' string must be injected when PermissionGuard is used.
I tried to test it with the following code.
permission.e2e.spec.ts
beforeAll(async () => {
const moduleRef: TestingModule = await Test.createTestingModule({
imports: [PermissionTestAppModule],
})
.compile()
app = moduleRef.createNestApplication()
controller = await moduleRef.resolve(PermissionTestController)
await app.init()
})
but it says,
Nest can't resolve dependencies of the PermissionGuard (?). Please make sure that the argument xoxo at index [0] is available in the PermissionTestAppModule context.
Potential solutions:
- Is PermissionTestAppModule a valid NestJS module?
- If xoxo is a provider, is it part of the current PermissionTestAppModule?
- If xoxo is exported from a separate #Module, is that module imported within PermissionTestAppModule?
#Module({
imports: [ /* the Module containing xoxo */ ]
})
What am I doing wrong?

NestJS/TypeORM unit testing: Can't resolve dependencies of JwtService

I'm trying to unit test this controller and mock away the services/repositories that it needs.
#Controller('auth')
export class AuthController {
constructor(
private readonly authService: AuthService,
private readonly usersService: UsersService,
) {}
#Post('register')
public async registerAsync(#Body() createUserModel: CreateUserModel) {
const result = await this.authenticationService.registerUserAsync(createUserModel);
// more code here
}
#Post('login')
public async loginAsync(#Body() login: LoginModel): Promise<{ accessToken: string }> {
const user = await this.usersService.getUserByUsernameAsync(login.username);
// more code here
}
}
Here is my unit test file:
describe('AuthController', () => {
let authController: AuthController;
let authService: AuthService;
beforeEach(async () => {
const moduleRef: TestingModule = await Test.createTestingModule({
imports: [JwtModule],
controllers: [AuthController],
providers: [
AuthService,
UsersService,
{
provide: getRepositoryToken(User),
useClass: Repository,
},
],
}).compile();
authController = moduleRef.get<AuthenticationController>(AuthenticationController);
authService = moduleRef.get<AuthenticationService>(AuthenticationService);
});
describe('registerAsync', () => {
it('Returns registration status when user registration succeeds', async () => {
let createUserModel: CreateUserModel = {...}
let registrationStatus: RegistrationStatus = {
success: true,
message: 'User registered successfully',
};
jest.spyOn(authService, 'registerUserAsync').mockImplementation(() =>
Promise.resolve(registrationStatus),
);
expect(await authController.registerAsync(createUserModel)).toEqual(registrationStatus);
});
});
});
But when running this, I get the following error(s):
● AuthController › registerAsync › Returns registration status when user registration succeeds
Nest can't resolve dependencies of the JwtService (?). Please make sure that the argument JWT_MODULE_OPTIONS at index [0] is available in the JwtModule context.
Potential solutions:
- If JWT_MODULE_OPTIONS is a provider, is it part of the current JwtModule?
- If JWT_MODULE_OPTIONS is exported from a separate #Module, is that module imported within JwtModule?
#Module({
imports: [ /* the Module containing JWT_MODULE_OPTIONS */ ]
})
at Injector.lookupComponentInParentModules (../node_modules/#nestjs/core/injector/injector.js:191:19)
at Injector.resolveComponentInstance (../node_modules/#nestjs/core/injector/injector.js:147:33)
at resolveParam (../node_modules/#nestjs/core/injector/injector.js:101:38)
at async Promise.all (index 0)
at Injector.resolveConstructorParams (../node_modules/#nestjs/core/injector/injector.js:116:27)
at Injector.loadInstance (../node_modules/#nestjs/core/injector/injector.js:80:9)
at Injector.loadProvider (../node_modules/#nestjs/core/injector/injector.js:37:9)
at Injector.lookupComponentInImports (../node_modules/#nestjs/core/injector/injector.js:223:17)
at Injector.lookupComponentInParentModules (../node_modules/#nestjs/core/injector/injector.js:189:33)
● AuthController › registerAsync › Returns registration status when user registration succeeds
Cannot spyOn on a primitive value; undefined given
48 | };
49 |
> 50 | jest.spyOn(authService, 'registerUserAsync').mockImplementation(() =>
| ^
51 | Promise.resolve(registrationStatus),
52 | );
53 |
at ModuleMockerClass.spyOn (../node_modules/jest-mock/build/index.js:780:13)
at Object.<anonymous> (Authentication/authentication.controller.spec.ts:50:18)
I'm not quite sure how to proceed so I'd like some help.
There's a few things I'm noticing here:
if you're testing the controller, you shouldn't need to mock more than one level deep pf services
you should almost never have a use case where you need an imports array in a unit test.
What you can do for your test case instead is something similar to the following:
beforeEach(async () => {
const modRef = await Test.createTestingModule({
controllers: [AuthController],
providers: [
{
provide: AuthService,
useValue: {
registerUserAsync: jest.fn(),
}
},
{
provide: UserService,
useValue: {
getUserByUsernameAsync: jest.fn(),
}
}
]
}).compile();
});
Now you can get the auth service and user service using modRef.get() and save them to a variable to add mocks to them later. You can check out this testing repository which has a lot of other examples.
Since you are registering AuthService in the dependency injection container and just spying on registerUserAsync, it requires JWTService to be registered as well.
You need to register dependencies that are injected in AuthService:
const moduleRef: TestingModule = await Test.createTestingModule({
imports: [JwtModule],
controllers: [AuthController],
providers: [
AuthService,
UsersService,
JWTService, // <--here
{
provide: getRepositoryToken(User),
useClass: Repository,
},
],
}).compile();
or register a fully mocked AuthService that doesn't need any other dependency:
const moduleRef: TestingModule = await Test.createTestingModule({
imports: [JwtModule],
controllers: [AuthController],
providers: [
{
provide: AuthService,
useValue: {
registerUserAsync: jest.fn(), // <--here
}
},
{
provide: getRepositoryToken(User),
useClass: Repository,
},
],
}).compile();
If you're building out a full integration test suite for NestJS then it will be easy to hit this error if you import a module that imports the AuthService. That will inevitably require the JwtService which will error out with: Nest can't resolve dependencies of the JwtService (?). Please make sure that the argument JWT_MODULE_OPTIONS at index [0] is available in the RootTestModule context.
Here's how I resolved this. I added:
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
secret: configService.get('JWT_SECRET'),
signOptions: { expiresIn: '1d' }
})
}),
To my imports: [] function inside my await Test.createTestingModule({ call
The final important thing to do was to not import JwtService directly. Instead, initialize JwtModule with the above code which by extension itself should internally initialize JwtService correctly.

Header are not passed through after updating nestjs/graphql

I'm about to update my project dependencies to the next major versions but i can't get nestjs/graphql + nestjs/passport to work.
It looks like the request header is not passed through apollo server. Everytime when passport tries to extract the bearer token from the header i get an exception with the following stacktrace:
TypeError: Cannot read property 'headers' of undefined,
at JwtStrategy._jwtFromRequest (/Users/wowa/workspace/foxcms-backend/node_modules/passport-jwt/lib/extract_jwt.js:58:21),
at JwtStrategy.authenticate (/Users/wowa/workspace/foxcms-backend/node_modules/passport-jwt/lib/strategy.js:93:22),
at attempt (/Users/wowa/workspace/foxcms-backend/node_modules/passport/lib/middleware/authenticate.js:361:16)",
at authenticate (/Users/wowa/workspace/foxcms-backend/node_modules/passport/lib/middleware/authenticate.js:362:7)",
at Promise (/Users/wowa/workspace/foxcms-backend/node_modules/#nestjs/passport/dist/auth.guard.js:77:3)",
at new Promise ()",
at /Users/wowa/workspace/foxcms-backend/node_modules/#nestjs/passport/dist/auth.guard.js:69:83",
at MixinAuthGuard. (/Users/wowa/workspace/foxcms-backend/node_modules/#nestjs/passport/dist/auth.guard.js:44:36)",
at Generator.next ()",
at /Users/wowa/workspace/foxcms-backend/node_modules/#nestjs/passport/dist/auth.guard.js:19:71"
This is how my app.module looks like:
#Module({
imports: [
GraphQLModule.forRoot({
typePaths: ['./src/**/*.graphql'],
}),
UserModule,
ContentTypeModule,
PrismaModule,
ProjectModule,
AuthModule,
],
})
export class AppModule implements NestModule {
constructor(
private readonly graphQLFactory: GraphQLFactory,
#Inject('PrismaBinding') private prismaBinding,
) {}
configure(consumer: MiddlewareConsumer) {}
}
I just wanted to ask here before i open an issue on github. Anyone a idea whats wrong?
You can manage the object request with this form:
GraphQLModule.forRoot({
typePaths: ['./**/*.graphql'],
installSubscriptionHandlers: true,
context: (({ req }) => {
return { request: req }
}),
},
And create your own Guard:
export class CatsGuard implements CanActivate {
constructor(readonly jwtService: JwtService/*, readonly userService: UsersService*/) {}
canActivate(context: ExecutionContext): boolean {
const ctx = GqlExecutionContext.create(context);
const request = ctx.getContext().request;
const Authorization = request.get('Authorization');
if (Authorization) {
const token = Authorization.replace('Bearer ', '');
const { userId } = this.jwtService.verify(token) as { userId: string };
return !!userId;
}
}
}
The provided AuthGuard from the passport module is currently not working with the graphql module.
https://github.com/nestjs/graphql/issues/48

Nest can't resolve dependencies of the VendorsService (?). Please verify whether [0] argument is available in thecurrent context

I am new to nest js and typescript also. Thanks in advance.
I am getting this error continuously.
Nest can't resolve dependencies of the VendorsService (?). Please verify whether [0] argument is available in thecurrent context.
Here is the code
App module
#Module({
imports: [ UsersModule, VendorsModule],
})
export class ApplicationModule {}
controller
#Controller()
export class VendorsController {
constructor(private readonly vendorService: VendorsService){}
#Post()
async create(#Body() createVendorDTO: CreateVendorDTO){
this.vendorService.create(createVendorDTO);
}
#Get()
async findAll(): Promise<Vendor[]>{
return this.vendorService.findAll();
}
}
Service
#Injectable()
export class VendorsService {
constructor(#Inject('VendorModelToken') private readonly vendorModel: Model<Vendor>) {}
async create(createVendorDTO: CreateVendorDTO): Promise<Vendor>{
const createdVendor = new this.vendorModel(createVendorDTO);
return await createdVendor.save();
}
async findAll(): Promise<Vendor[]>{
return await this.vendorModel.find().exec();
}
}
provider
export const usersProviders = [
{
provide: 'VendorModelToken',
useFactory: (connection: Connection) => connection.model('Vendor', VendorSchema),
inject: ['DbConnectionToken'],
},
];
Module
#Module({
providers: [VendorsService],
controllers: [VendorsController],
})
export class VendorsModule {}
VendorsModule sould declare your provider (usersProviders) in its providers, otherwise Nestjs will never be able to inject it into your service.
Unless you wanted to declare it with UsersModule (I guess you did); in that case, UsersModule also needs it in its exports so it's made visible to other modules importing UsersModule.
It's either VendorsModule: usersProviders in providers,
Or UsersModule: usersProviders in both providers and exports

Resources