I am creating an authentication system via UseGuards, but how can I inject dependencies into my guards?
I'd like to do it in a global way, to avoid repeating code and every controller importing the injections.
I am using the mode of dependency inversion and injecting the classes, I am also making the classes only depend on implementations and interface rules...
My AuthServices
export class AuthServices implements IMiddlewareAuth {
constructor(
private readonly jwt: IJWTDecrypt,
private readonly authUserRepo: IAuthUserContract,
) {}
public async intercept(
req: IMiddlewareAuth.Attrs,
): Promise<IMiddlewareAuth.Return> {
const token = req.headers['Authorization'];
if (!token) {
new Fails('Access denied', 403);
}
const parts = token.split(' ');
if (parts.length !== 2) {
return new Fails('Anauthorized', 401);
}
const [scheme, access] = parts;
if (!/^Bearer$/i.test(scheme)) {
return new Fails('Anauthorized', 400);
}
const id = await this.jwt.decrypt(access);
if (!id) {
return new Fails('Anauthorized', 400);
}
const user = await this.authUserRepo.findById(id);
if (!user) {
return new Fails('Anauthorized', 400);
}
return user;
}
}
my Auth.Guards
#Injectable()
export class AuthorizationGuard implements CanActivate {
constructor(
#Inject('AuthServices')
private authServices: IMiddlewareAuth,
) {}
public async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const user = await this.authServices.intercept(request);
if (user instanceof Fails) {
throw new HttpException(
{
statusCode: user.statusCode,
error: user.message,
},
user.statusCode,
);
}
request.user = {
email: user.email,
id: user.id,
name: user.name,
} as ISessionLogin;
return true;
}
}
my Auth.Module
#Module({
imports: [ConfigModule.forRoot(), TypeOrmModule.forFeature([UserEntity])],
providers: [
{
provide: AuthUserRepository,
useFactory: (dataSource: DataSource) => {
return new AuthUserRepository(
dataSource.getMongoRepository(UserEntity),
);
},
inject: [getDataSourceToken()],
},
{
provide: JsonWebToken,
useFactory: () => {
return new JsonWebToken();
},
},
{
provide: AuthServices,
useFactory: (
jwt: JsonWebToken,
authUserRepository: AuthUserRepository,
) => {
return new AuthServices(jwt, authUserRepository);
},
inject: [JsonWebToken, AuthUserRepository],
},
],
exports: [AuthServices],
})
export class AuthModule {}
Error
Use #Inject(AuthService) instead of #Inject('AuthService'). The important distinction is that 'AuthService' is a string token that matches AuthService.name and AuthService is a class reference. Also, make sure the CreateUsersModule has AuthModule in its imports array
Related
I have an external service providing a JWT token. In Nestjs i first have JwtGuard class:
#Injectable()
export class JwtGuard extends AuthGuard('JWT_STRATEGY') {
constructor() {
super();
}
getRequest(context: ExecutionContext) {
console.log('JwtGuard');
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req;
}
}
and then a passport strategy:
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'JWT_STRATEGY') {
constructor(private configService: ConfigService) {
super({
secretOrKeyProvider: passportJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: configService.get<string>('ADFS_KEYS_URL'),
}),
ignoreExpiration: false,
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
audience: configService.get<string>('ADFS_AUDIENCE'),
issuer: configService.get<string>('ADFS_ISSUER'),
algorithms: ['RS256'],
});
}
validate(payload: unknown): unknown {
console.log('jwt strategy');
console.log(payload);
return payload;
}
}
It seems that JwtGuard is running first, then the strategy. But if i want to do additional guards and checks, say for roles. Where does one do that? Do i need another guard that runs after the passport strategy? I have two roles "User" and "Admin".
First of all, define a global guard (called RolesGuard) in the AppModule as following:
providers: [
AppService,
{
provide: APP_GUARD,
useClass: JwtAuthGuard,
},
{
provide: APP_GUARD,
useClass: RolesGuard,
},
]
Then within RolesGuard we have the following:
export enum FamilyRole {
Admin = 'Admin',
User = 'User',
}
...
export class FamilyRolesGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const requiredRoles = this.reflector.getAllAndOverride<FamilyRole>(
ROLES_KEY,
[context.getHandler(), context.getClass()],
);
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
// do the rest and return either true or false
}
}
Then create your own decorator and you can decorate your APIs if you need that API to protect your app based on your guard.
import { SetMetadata } from '#nestjs/common';
export const ROLES_KEY = 'FamilyRoles';
export const FamilyRoles = (...roles: FamilyRole[]) =>
SetMetadata(ROLES_KEY, roles);
Then you can use your decorator in your API like this:
#Post('user')
#FamilyRoles(FamilyRole.Admin)
...
So, in your API, if you won't have FamilyRoles, in the guard you won't have requiredRoles, and the if block will return true.
More info: https://docs.nestjs.com/security/authorization
I have an existing authentication for users which is already working fine. The token for user authentication expires within an hour.
I want to implement another separate authentication strategy a third API that is consuming my Nestjs API. There are separate endpoints for the third-party API, the token should expire with 24 hours. The API has to stay connected to my app for 24 hours.
I don't mind using additional package to achieve this.
I also need to create a guard called thirdParty Guard so that the 3rd part API alone will have access to that endpoint.
This is my jwt.strategy.ts
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.SECRETKEY
});
}
async validate(payload: any, done: VerifiedCallback) {
const user = await this.authService.validateUser(payload);
if (!user) {
return done(
new HttpException('Unauthorised access', HttpStatus.UNAUTHORIZED),
false,
);
}
//return user;
return done(null, user, payload.iat)
}
}
ApiKey.strategy.ts
#Injectable()
export class ApiKeyStrategy extends PassportStrategy(HeaderAPIKeyStrategy) {
constructor(private authService: AuthService) {
super({
header: 'api_key',
prefix: ''
}, true,
(apikey: string, done: any, req: any, next: () => void) => {
const checkKey = this.authService.validateApiKey(apikey);
if (!checkKey) {
return done(
new HttpException('Unauthorized access, verify the token is correct', HttpStatus.UNAUTHORIZED),
false,
);
}
return done(null, true, next);
});
}
}
and this is the auth.service.ts
#Injectable()
export class AuthService {
constructor(private userService: UserService) { }
async signPayLoad(payload: any) {
return sign(payload, process.env.SECRETKEY, { expiresIn: '1h' });
}
async validateUser(payload: any) {
const returnuser = await this.userService.findByPayLoad(payload);
return returnuser;
}
validateApiKey(apiKey: string) {
const keys = process.env.API_KEYS;
const apiKeys = keys.split(',');
return apiKeys.find(key => apiKey === key);
}
}
With the above setup, If you are using Passport-HeaderAPIKey then try adding headerapikey in the Guard. The below code worked for me.
Ref: NestJS extending guard
import { ExecutionContext, Injectable } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
import { AuthGuard as NestAuthGuard } from '#nestjs/passport';
#Injectable()
export class AuthGuard extends NestAuthGuard(['jwt', 'headerapikey']) {
constructor(private readonly reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride<boolean>('isPublic', [
context.getHandler(),
context.getClass(),
]);
if (isPublic) {
return true;
}
return super.canActivate(context);
}
}
it's my first time making questions on stackoverflow :).
So I'm developing a test / learning application to learn how to use NestJS and Vue.
I am was currently trying to implement several server-side unit tests (using Jest). When trying to create a testingmodule from my UsersService I get the error below on running "npm test users.service.spec"
Nest can't resolve dependencies of the UsersService (?,
ConfigService). Please make sure that the argument at index [0] is
available in the _RootTestModule context.
at Injector.lookupComponentInExports (../node_modules/#nestjs/core/injector/injector.js:183:19)
I am not sure if I am incorrectly instantiating the test model, or misconceived the injection of config.service, the code itself works, but may be implemented incorrectly.
Does anyone have any ideas how to solve this problem?
Git link: https://github.com/Lindul/FuelTracker_NestJs_MySql_Vue.git
users.service.spec.ts
import { Test } from '#nestjs/testing';
import { UsersService } from './users.service';
import { UserDto } from './dto/user.dto';
import { UserLoginRegRequestDto } from './dto/user-login-reg-request.dto';
import { ConfigService } from '../shared/config/config.service';
describe('User Service Tests', () => {
let loginTeste: UserLoginRegRequestDto = {
login: 'LoginJestTeste',
password: 'PassJestTeste',
}
const userMock: UserDto = {
id: 1,
login: 'hugombsantos',
password: 'wefewkfnwekfnwekjnf',
isActive: true,
needChangePass: false,
userType: 0,
};
const dataFindAllMock: UserDto[] = [{
id: 1,
login: 'hugombsantos',
password: 'wefewkfnwekfnwekjnf',
isActive: true,
needChangePass: false,
userType: 0,
}, {
id: 2,
login: 'user2',
password: 'sdgsdgdsgsdgsgsdg',
isActive: true,
needChangePass: false,
userType: 0,
}];
let service: UsersService;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
ConfigService,
UsersService,
],
}).compile();
service = module.get<UsersService>(UsersService);
});
it('UsersService está defenido', () => {
expect(service).toBeDefined();
});
});
user.service.ts
import { Injectable, Inject, HttpException, HttpStatus } from '#nestjs/common';
import { Users } from '../models/users.schema';
import { UserDto } from './dto/user.dto';
import { UserLoginRegRequestDto } from './dto/user-login-reg-request.dto';
import { UserLoginResponseDto } from './dto/user-login-response.dto';
import { sign } from 'jsonwebtoken';
import { ConfigService } from '../shared/config/config.service';
import { JwtPayload } from './auth/jwt-payload.model';
import { ErroMessageDto } from '../shared/config/dto/erro-message.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import bcrypt = require('bcrypt-nodejs');
const SALT_FACTOR = 8;
#Injectable()
export class UsersService {
constructor(
#Inject('UsersRepository') private usersRepository: typeof Users,
private readonly configService: ConfigService,
) { }
/* istanbul ignore next */
async findAll(): Promise<UserDto[]> {
const users = await this.usersRepository.findAll<Users>();
return users.map(user => {
return new UserDto(user);
});
}
/* istanbul ignore next */
async findUser(id: number): Promise<Users> {
const user = await this.usersRepository.findOne<Users>({
where: { id },
});
return user;
}
/* istanbul ignore next */
async findUserformLogin(login: string): Promise<UserDto> {
return await this.usersRepository.findOne<Users>({
where: { login },
});
}
/* istanbul ignore next */
async findUserforLogin(id: number, login: string): Promise<Users> {
return await this.usersRepository.findOne<Users>({
where: { id, login },
});
}
/* istanbul ignore next */
async creatUserOnDb(createUser: UserLoginRegRequestDto): Promise<Users> {
try {
const user = new Users();
user.login = createUser.login;
user.password = await this.hashPass(createUser.password);
return await user.save();
} catch (err) {
if (err.original.code === 'ER_DUP_ENTRY') {
throw new HttpException(
new ErroMessageDto(
HttpStatus.CONFLICT,
`Login '${err.errors[0].value}' already exists`),
HttpStatus.CONFLICT,
);
}
throw new HttpException(
new ErroMessageDto(
HttpStatus.INTERNAL_SERVER_ERROR,
err),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
/* istanbul ignore next */
async updateUserOnDb(user: Users): Promise<Users> {
try {
return await user.save();
} catch (err) {
if (err.original.code === 'ER_DUP_ENTRY') {
throw new HttpException(
new ErroMessageDto(
HttpStatus.CONFLICT,
`Login '${err.errors[0].value}' already exists`),
HttpStatus.CONFLICT,
);
}
throw new HttpException(
new ErroMessageDto(
HttpStatus.INTERNAL_SERVER_ERROR,
err),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
/* istanbul ignore next */
async delete(id: number): Promise<UserDto> {
const user = await this.findUser(+id);
await user.destroy();
return new UserDto(user);
}
async login(
userLoginRequestDto: UserLoginRegRequestDto,
): Promise<UserLoginResponseDto> {
const login = userLoginRequestDto.login;
const password = userLoginRequestDto.password;
const user = await this.findUserformLogin(login);
if (!user) {
throw new HttpException(
new ErroMessageDto(
HttpStatus.UNAUTHORIZED,
'Invalid login or password.'),
HttpStatus.UNAUTHORIZED,
);
}
const isMatch = await this.comparePass(password, user.password);
if (!isMatch) {
throw new HttpException(
new ErroMessageDto(
HttpStatus.UNAUTHORIZED,
'Invalid login or password.'),
HttpStatus.UNAUTHORIZED,
);
}
const token = await this.signToken(user);
return new UserLoginResponseDto(token);
}
async create(createUser: UserLoginRegRequestDto): Promise<UserLoginResponseDto> {
const userData = await this.creatUserOnDb(createUser);
// when registering then log user in automatically by returning a token
const token = await this.signToken(userData);
return new UserLoginResponseDto(token);
}
async updateUser(id: number | string, updateUserDto: UpdateUserDto): Promise<UserLoginResponseDto> {
const user = await this.findUser(+id);
if (!user) {
throw new HttpException(
new ErroMessageDto(
HttpStatus.NOT_FOUND,
'User not found.'),
HttpStatus.NOT_FOUND,
);
}
user.login = updateUserDto.login || user.login;
user.userType = updateUserDto.userType || user.userType;
user.needChangePass = false;
const isMatch = await this.comparePass(updateUserDto.password, user.password);
if (updateUserDto.password && !isMatch) {
user.password = await this.hashPass(updateUserDto.password);
} else {
user.password = user.password;
}
const userData = await this.updateUserOnDb(user);
const token = await this.signToken(userData);
return new UserLoginResponseDto(token);
}
async signToken(user: UserDto): Promise<string> {
const payload: JwtPayload = {
id: user.id,
login: user.login,
};
const token = sign(payload, this.configService.jwtConfig.jwtPrivateKey,
{
expiresIn: this.configService.jwtConfig.valideTime,
});
return token;
}
async hashPass(passToHash: string): Promise<string> {
const saltValue = await bcrypt.genSaltSync(SALT_FACTOR);
return await bcrypt.hashSync(passToHash, saltValue);
}
async comparePass(passToCheck: string, correntPass: string) {
return await bcrypt.compareSync(passToCheck, correntPass);
}
}
users.modules.ts
import { Module } from '#nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import {userProviders} from './users.providers';
import { DatabaseModule } from '../database/database.module';
import { JwtStrategy } from './auth/jwt-strategy';
import { ConfigService } from '../shared/config/config.service';
#Module({
imports: [DatabaseModule, ConfigService],
controllers: [UsersController],
providers: [UsersService,
...userProviders, JwtStrategy],
exports: [UsersService],
})
export class UsersModule {}
config.service.ts
import { Injectable } from '#nestjs/common';
import { JwtConfig } from './interfaces/jwt-config.interface';
import { config } from '../../../config/config.JwT';
import { config as dbConfigData } from '../../../config/config.db';
import { SequelizeOrmConfig } from './interfaces/sequelize-orm-config.interface';
#Injectable()
export class ConfigService {
get sequelizeOrmConfig(): SequelizeOrmConfig {
return dbConfigData.databaseOpt;
}
get jwtConfig(): JwtConfig {
return {
jwtPrivateKey: config.jwtPrivateKey,
valideTime: config.valideTime,
};
}
}
You need to provide some value for Nest to inject for your UserRepository. It looks like you are using the TypeORM #InjectRepository() decorator, so in your testing module you'll need to add something like this under your providers
{
provide: getRepositoryToken('UsersRepository'),
useValue: MyMockUserRepo,
}
So that Nest can know what value to inject for that string. MyMockUserRepo would be your mock repository functionality, so that you aren't making actual database calls to your backend
I am trying to set up a very simple login system using jwt-passport with nestjs. I followed this tutorial: https://docs.nestjs.com/techniques/authentication but I just can't get it to work. I am really new to this stuff and would appreciate if anyone can show me the way.
The way I send the login to the server:
this.clientAuthService.login(this.userName, this.password).then(response => {
this.clientAuthService.setToken(response.access_token);
this.router.navigate(['/backend']);
});
My ClientAuthService:
export class ClientAuthService {
constructor(private http: HttpClient, #Inject(PLATFORM_ID) private platformId) {
}
getToken(): string {
if (isPlatformBrowser(this.platformId)) {
return localStorage.getItem(TOKEN_NAME);
} else {
return '';
}
}
setToken(token: string): void {
if (isPlatformBrowser(this.platformId)) {
localStorage.setItem(TOKEN_NAME, token);
}
}
removeToken() {
if (isPlatformBrowser(this.platformId)) {
localStorage.removeItem(TOKEN_NAME);
}
}
getTokenExpirationDate(token: string): Date {
const decoded = jwt_decode(token);
if (decoded.exp === undefined) {
return null;
}
const date = new Date(0);
date.setUTCSeconds(decoded.exp);
return date;
}
isTokenExpired(token?: string): boolean {
if (!token) {
token = this.getToken();
}
if (!token) {
return true;
}
const date = this.getTokenExpirationDate(token);
if (date === undefined) {
return false;
}
return !(date.valueOf() > new Date().valueOf());
}
login(userName: string, password: string): Promise<any> {
const loginData = {username: userName, password};
return this.http
.post(Constants.hdaApiUrl + 'user/login', loginData, {headers: new HttpHeaders({'Content-Type': 'application/json'})})
.toPromise();
}
}
My user.controller.ts
#Controller('user')
export class UserController {
constructor(private readonly authService: AuthService) {
}
#UseGuards(AuthGuard('local'))
#Post('login')
authenticate(#Request() req) {
return this.authService.login(req);
}
}
My user.service.ts
export class UsersService {
private readonly users: User[];
constructor() {
this.users = [
{
userId: 1,
username: 'test',
password: '12345',
}
];
}
async findOne(username: string): Promise<User | undefined> {
return this.users.find(user => user.username === username);
}
}
Then I have the jwt.strategy.ts
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: Constants.jwtSecret,
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}
and the local.strategy.ts
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super();
}
async validate(username: string, password: string): Promise<any> {
const user = await this.authService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
Mostly I just followed the tutorial and added some stuff for the client-side by myself.
I missed the part with the UseGuard('local') for the login route but after I added it I am getting 401 error always.
When I don't use UseGuard('local') it doesn't matter what I type in the login form. After I submit the details, I get access to the backend even tho it was not correct.
Also, it might be worth to mention that the validate methods in jwt.strategy.ts and local.strategy.ts are marked as not used in WebStorm.
I know its a lot of code here but I need help because I cannot find any other sources for NestJS auth configuration which is up to date. It feels like the tutorial I followed missed a lot of steps for beginners.
Make sure your post body (payload) is identical to the signature of the validate method (it actually has to be username & password).
In order to add a bit more context, in the above picture you can find detail where the implementation of the specified signature is mentioned. So it's mandatory to send exact "username" and "password" properties in the body.
Note: if you want to customize the signature of your local strategy service you simply have to pass a new object within the super() for specifying the new properties:
constructor() {
super({ usernameField: 'email' });
}
Ref: https://docs.nestjs.com/techniques/authentication
For me, in multiple projects,
Using
export class JwtStrategy extends PassportStrategy(Strategy, 'theJwt')
with #UseGuards(AuthGuard('theJwt'))
rather than using:
export class JwtStrategy extends PassportStrategy(Strategy)
with
#UseGuards(AuthGuard(JwtStrategy )), which did not work.
Working on a project with Nestjs 6.x, Mongoose, Mongo, etc...
Regarding to the Back End, in my use case, I must change the connection of one of my databases depending of some conditions/parameters coming from some requests.
Basically, I have this
mongoose.createConnection('mongodb://127.0.0.1/whatever-a', { useNewUrlParser: true })
and I want to change to, for example
mongoose.createConnection('mongodb://127.0.0.1/whatever-b', { useNewUrlParser: true })
Therefore, I have in Nestjs the first provider
export const databaseProviders = [
{
provide: 'DbConnectionToken',
useFactory: async (): Promise<typeof mongoose> =>
await mongoose.createConnection('mongodb://127.0.0.1/whatever', { useNewUrlParser: true })
}
I was researching for a while and I found out that in release Nestjs 6.x there are provider requests allowing me to modify dynamically Per-request the injection of some providers.
Anyway, I don't know how to achieve my change neither if it is going to be working in case I'd achieve that
Can anyone help or guide me?
Many thanks in advance.
You can do the following using Nest's built-in Mongoose package:
/*************************
* mognoose.service.ts
*************************/
import { Inject, Injectable, Scope } from '#nestjs/common';
import { MongooseOptionsFactory, MongooseModuleOptions } from '#nestjs/mongoose';
import { REQUEST } from '#nestjs/core';
import { Request } from '#nestjs/common';
#Injectable({ scope: Scope.REQUEST })
export class MongooseConfigService implements MongooseOptionsFactory {
constructor(
#Inject(REQUEST) private readonly request: Request,) {
}
createMongooseOptions(): MongooseModuleOptions {
return {
uri: request.params.uri, // Change this to whatever you want; you have full access to the request object.
};
}
}
/*************************
* mongoose.module.ts
*************************/
import { Module } from '#nestjs/common';
import { MongooseModule } from '#nestjs/mongoose';
import { MongooseConfigService } from 'mognoose.service';
#Module({
imports: [
MongooseModule.forRootAsync({
useClass: MongooseConfigService,
}),
]
})
export class DbModule {}
Then, you can attach whatever you want to the request and change the database per request; hence the use of the Scope.REQUEST. You can read more about Injection Scopes on their docs.
Edit: If you run into issues with PassportJS (or any other package) or the request is empty, it seems to be an error that relates to PassportJS (or the other package) not supporting request scopes; you may read more about the issue on GitHub regarding PassportJS.
I did a simple implementation for nest-mongodb,
The main changes are in mongo-core.module.ts where I store the connections in a map and used them if available instead of creating a new connection every time.
import {
Module,
Inject,
Global,
DynamicModule,
Provider,
OnModuleDestroy,
} from '#nestjs/common';
import { ModuleRef } from '#nestjs/core';
import { MongoClient, MongoClientOptions } from 'mongodb';
import {
DEFAULT_MONGO_CLIENT_OPTIONS,
MONGO_MODULE_OPTIONS,
DEFAULT_MONGO_CONTAINER_NAME,
MONGO_CONTAINER_NAME,
} from './mongo.constants';
import {
MongoModuleAsyncOptions,
MongoOptionsFactory,
MongoModuleOptions,
} from './interfaces';
import { getClientToken, getContainerToken, getDbToken } from './mongo.util';
import * as hash from 'object-hash';
#Global()
#Module({})
export class MongoCoreModule implements OnModuleDestroy {
constructor(
#Inject(MONGO_CONTAINER_NAME) private readonly containerName: string,
private readonly moduleRef: ModuleRef,
) {}
static forRoot(
uri: string,
dbName: string,
clientOptions: MongoClientOptions = DEFAULT_MONGO_CLIENT_OPTIONS,
containerName: string = DEFAULT_MONGO_CONTAINER_NAME,
): DynamicModule {
const containerNameProvider = {
provide: MONGO_CONTAINER_NAME,
useValue: containerName,
};
const connectionContainerProvider = {
provide: getContainerToken(containerName),
useFactory: () => new Map<any, MongoClient>(),
};
const clientProvider = {
provide: getClientToken(containerName),
useFactory: async (connections: Map<any, MongoClient>) => {
const key = hash.sha1({
uri: uri,
clientOptions: clientOptions,
});
if (connections.has(key)) {
return connections.get(key);
}
const client = new MongoClient(uri, clientOptions);
connections.set(key, client);
return await client.connect();
},
inject: [getContainerToken(containerName)],
};
const dbProvider = {
provide: getDbToken(containerName),
useFactory: (client: MongoClient) => client.db(dbName),
inject: [getClientToken(containerName)],
};
return {
module: MongoCoreModule,
providers: [
containerNameProvider,
connectionContainerProvider,
clientProvider,
dbProvider,
],
exports: [clientProvider, dbProvider],
};
}
static forRootAsync(options: MongoModuleAsyncOptions): DynamicModule {
const mongoContainerName =
options.containerName || DEFAULT_MONGO_CONTAINER_NAME;
const containerNameProvider = {
provide: MONGO_CONTAINER_NAME,
useValue: mongoContainerName,
};
const connectionContainerProvider = {
provide: getContainerToken(mongoContainerName),
useFactory: () => new Map<any, MongoClient>(),
};
const clientProvider = {
provide: getClientToken(mongoContainerName),
useFactory: async (
connections: Map<any, MongoClient>,
mongoModuleOptions: MongoModuleOptions,
) => {
const { uri, clientOptions } = mongoModuleOptions;
const key = hash.sha1({
uri: uri,
clientOptions: clientOptions,
});
if (connections.has(key)) {
return connections.get(key);
}
const client = new MongoClient(
uri,
clientOptions || DEFAULT_MONGO_CLIENT_OPTIONS,
);
connections.set(key, client);
return await client.connect();
},
inject: [getContainerToken(mongoContainerName), MONGO_MODULE_OPTIONS],
};
const dbProvider = {
provide: getDbToken(mongoContainerName),
useFactory: (
mongoModuleOptions: MongoModuleOptions,
client: MongoClient,
) => client.db(mongoModuleOptions.dbName),
inject: [MONGO_MODULE_OPTIONS, getClientToken(mongoContainerName)],
};
const asyncProviders = this.createAsyncProviders(options);
return {
module: MongoCoreModule,
imports: options.imports,
providers: [
...asyncProviders,
clientProvider,
dbProvider,
containerNameProvider,
connectionContainerProvider,
],
exports: [clientProvider, dbProvider],
};
}
async onModuleDestroy() {
const clientsMap: Map<any, MongoClient> = this.moduleRef.get<
Map<any, MongoClient>
>(getContainerToken(this.containerName));
if (clientsMap) {
await Promise.all(
[...clientsMap.values()].map(connection => connection.close()),
);
}
}
private static createAsyncProviders(
options: MongoModuleAsyncOptions,
): Provider[] {
if (options.useExisting || options.useFactory) {
return [this.createAsyncOptionsProvider(options)];
} else if (options.useClass) {
return [
this.createAsyncOptionsProvider(options),
{
provide: options.useClass,
useClass: options.useClass,
},
];
} else {
return [];
}
}
private static createAsyncOptionsProvider(
options: MongoModuleAsyncOptions,
): Provider {
if (options.useFactory) {
return {
provide: MONGO_MODULE_OPTIONS,
useFactory: options.useFactory,
inject: options.inject || [],
};
} else if (options.useExisting) {
return {
provide: MONGO_MODULE_OPTIONS,
useFactory: async (optionsFactory: MongoOptionsFactory) =>
await optionsFactory.createMongoOptions(),
inject: [options.useExisting],
};
} else if (options.useClass) {
return {
provide: MONGO_MODULE_OPTIONS,
useFactory: async (optionsFactory: MongoOptionsFactory) =>
await optionsFactory.createMongoOptions(),
inject: [options.useClass],
};
} else {
throw new Error('Invalid MongoModule options');
}
}
}
Check out the full implementation