Nest.js: Global AuthGuard but with exceptions - nestjs

I would like to register my AuthenticationGuard, which checks for Authentication, globally on my application, so that by default all routes require authentication.
const authGuard = app
.select(AuthModule)
.get(AuthGuard);
app.useGlobalGuards(authGuard);
What is the best/nest.js way to add route exceptions, so that anonymous routes can also be implemented?

You can actually set metadata for the global AuthGuard so it can determine if it should allow an unauthorized request.
e.g.
Set Global Auth Guard
import { Module } from '#nestjs/common';
import { APP_GUARD } from '#nestjs/core';
import { AuthGuard } from './auth.guard';
#Module({
providers: [
{
provide: APP_GUARD,
useClass: AuthGuard,
},
],
})
export class AppModule {}
Use SetMetadata to pass in data to the AuthGuard
import { SetMetadata } from '#nestjs/common';
// Convienience Function
const AllowUnauthorizedRequest = () => SetMetadata('allowUnauthorizedRequest', true);
#Controller()
export class AppController {
#Get('my-unauthorized-path')
#AllowUnauthorizedRequest()
myHandler () {
return { unauthorized: true };
}
}
Use data passed in from SetMetadata to determine if unauthorized request is allowed.
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
import { validateRequest } from './validateRequest' // your custom implementation
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
const allowUnauthorizedRequest = this.reflector.get<boolean>('allowUnauthorizedRequest', context.getHandler());
return allowUnauthorizedRequest || validateRequest(request);
}
}

There is no way to configure such behavior in a succinct way. If useGlobalGuards used, the only way to do this is to customize or extend AuthGuard.
See original issue

Related

NestJS / TypeORM: Custom repository method is not accessible in service

New to NestJS and TypeORM, and the similar questions on SO didn't solve my problem.
I have a custom TypeORM repository in NestJS using it in service, but it fails with error:
TypeError: this.tenantRepository.createTenant is not a function.
tenants.module.ts:
import { TenantRepository } from './tenant.repository';
#Module({
imports: [
TypeOrmModule.forFeature([TenantRepository]),
],
controllers: [TenantsController],
providers: [TenantsService],
})
export class TenantsModule { }
tenant.repository.ts:
// ...
import { TenantEntity } from './entities/tenant.entity';
#EntityRepository(TenantEntity)
export class TenantRepository extends Repository<TenantEntity>{
async createTenant(createTenantDto: CreateTenantDto): Promise<TenantEntity> {
const { name, email } = createTenantDto;
const newTenant = new TenantEntity()
newTenant.name = name;
newTenant.email = email;
await newTenant.save()
return newTenant;
}
}
And here's where the error is triggered (tenants.service.ts)
// ...
import { TenantEntity } from './entities/tenant.entity';
import { TenantRepository } from './tenant.repository';
#Injectable()
export class TenantsService {
constructor(
#InjectRepository(TenantRepository)
private tenantRepository: TenantRepository
) { }
async createTenant(createTenantDto: CreateTenantDto): Promise<TenantEntity> {
return await this.tenantRepository.createTenant(createTenantDto); // <-- ERROR
}
}
I can inject entity in service and use it for simple CRUD, but I want to separate concerns and use the repository pattern.
This is a POST endpoint and the error is only after submission from Swagger.
Also, VS Code autocomplete is suggesting createTenant after typing this.tenantRepository
Where am I going wrong?
EntityRepository decorator was deprecated, and as far as I know, you need to define a custom class that extends Repository and decorate it with #Injectable. Hence, you need to have some changes as follows:
tenant.repository.ts:
import { Injectable } from '#nestjs/common';
import { DataSource, Repository } from 'typeorm';
#Injectable()
export class TenantRepository extends Repository<TenantEntity>{
constructor(private dataSource: DataSource) {
super(TenantEntity, dataSource.createEntityManager());
}
async createTenant(createTenantDto: CreateTenantDto): Promise<TenantEntity> {
const { name, email } = createTenantDto;
const newTenant = this.create({ name, email });
await this.save(newTenant);
return newTenant;
}
}
tenants.module.ts:
import { TenantRepository } from './tenant.repository';
#Module({
imports: [
TypeOrmModule.forFeature([TenantRepository]),
],
controllers: [TenantsController],
providers: [TenantsService, TenantRepository],
})
export class TenantsModule { }
tenants.service.ts:
import { TenantEntity } from './entities/tenant.entity';
import { TenantRepository } from './tenant.repository';
#Injectable()
export class TenantsService {
constructor(
private tenantRepository: TenantRepository
) { }
async createTenant(createTenantDto: CreateTenantDto): Promise<TenantEntity> {
return await this.tenantRepository.createTenant(createTenantDto);
}
}
You also have access to built-in typeorm methods like save, create, find, etc. since the custom repository is derived from Repository class.

User and its data is undefined on the context.switchToHttp().getRequest() with nestjs guards

I'm new to nestJs and I needed to add role based access to the application so I followed the documentation but in the execution context user doesn't exist. I can't seems to find the problem
here is the full repo https://github.com/callme-MJ/test-sample
roles.guard.ts
import { CanActivate, ExecutionContext, Injectable } from "#nestjs/common";
import { Reflector } from "#nestjs/core";
import { use } from "passport";
import { Role } from "src/typeorm/roles.enum";
#Injectable()
export class RolesGuard implements CanActivate{
constructor (private reflector:Reflector){}
canActivate(context: ExecutionContext): boolean {
const requiredRole = this.reflector.getAllAndOverride<Role[]>('roles',[
context.getHandler(),
context.getClass(),
]);
if (!requiredRole) {
return true
}
const {user} = context.switchToHttp().getRequest();
return requiredRole.some((role)=> user.role.includes(role));
}
}
auth.controller.ts
import { Controller, Get, Post, Request, UseGuards } from '#nestjs/common';
import { AuthService } from 'src/auth/services/auth/auth.service';
import { Role } from 'src/typeorm/roles.enum';
import { JwtAuthGuard } from 'src/utils/guards/jwt.auth.guard';
import { LocalAuthGuard } from 'src/utils/guards/local-auth.guards';
import { RolesGuard } from 'src/utils/guards/roles.guards';
import { Roles } from 'src/utils/roles.decorator';
#Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) { }
#UseGuards(JwtAuthGuard,RolesGuard)
#Get('dashboard')
#Roles(Role.CORDINATOR)
getResponse(#Request() req): any {
return req.user;
}
}
At this line in your repo you have APP_GUARD which is a global guard binding. This means that this RolesGuard will be registered and used before your JwtAuthGuard in the getResponse method of the AuthController. Take a look at the section on "Enabling Authentication Globally" for a way around this. Otherwise, you can remove the APP_GUARD for the RolesGuard and just bind it where necessar

How can I user configService in super()?

I have a question about setting environmental variables.
In official document, it says using ConfigModule in this case, but my case is a exception case.
Because I would like to use it in super() in constructor.
My code is the below.
Is there any solution in this case?
If you need more information, please let me know.
Thank you all your support!!
// jwt.strategy.ts
import { UnauthorizedException } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { PassportStrategy } from '#nestjs/passport';
import { InjectRepository } from '#nestjs/typeorm';
import { Strategy, ExtractJwt } from 'passport-jwt';
import { JwtPayload } from './jwt-payload.interface';
import { UserRepository } from './user.repository';
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
#InjectRepository(UserRepository)
private userRepository: UserRepository,
private configService: ConfigService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: configService.get('JWT_TOKEN'),
});
}
async validate(payload: JwtPayload) {
const { username } = payload;
const user = await this.userRepository.findOne({ username });
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
For your Strategy you're missing the #Injectable() which tells Nest that it needs to inject the dependencies defined in the constructor.
You need to import the configModule into your module class for the configService to work. Also, add #Injectable() right above the class name to indicate it is a provider.
This is how you import the module.
//auth.module.ts
import { Module } from '#nestjs/common';
import { ConfigModule } from '#nestjs/config';
import { JwtStrategy } from './jwt.strategy';
#Module({
imports: [ConfigModule],
provider:[JwtStrategy]
})
export class AuthModule {}
NestJs resolves the dependencies between them.
see: https://docs.nestjs.com/techniques/configuration#using-the-configservice
I would also like to point out that since you are not using the configService anywhere but in the super() call the usage of private keyword in front of it is redundant
You can try using this.configService.get('JWT_TOKEN') but it will simply yell at you that you haven't called super
Removing the private keyword will simply avoid making the configService as a class variable and simply treat it as some option passed into it

How To Mock Repository, Service and Controller In NestJS (Typeorm & Jest)

I'm new at typescript. My Nestjs project app is something like this. I'm trying to use repository pattern, so i separated business logic (service) and persistance logic (repository)
UserRepository
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { UserEntity } from './entities/user.entity';
#Injectable()
export class UserRepo {
constructor(#InjectRepository(UserEntity) private readonly repo: Repository<UserEntity>) {}
public find(): Promise<UserEntity[]> {
return this.repo.find();
}
}
UserService
import { Injectable } from '#nestjs/common';
import { UserRepo } from './user.repository';
#Injectable()
export class UserService {
constructor(private readonly userRepo: UserRepo) {}
public async get() {
return this.userRepo.find();
}
}
UserController
import { Controller, Get } from '#nestjs/common';
import { UserService } from './user.service';
#Controller('/users')
export class UserController {
constructor(private readonly userService: UserService) {}
// others method //
#Get()
public async getUsers() {
try {
const payload = this.userService.get();
return this.Ok(payload);
} catch (err) {
return this.InternalServerError(err);
}
}
}
How do i create unit testing for repository, service & controller without actually persist or retrieve data to DB (using mock)?
Mocking in NestJS is pretty easily obtainable using the testing tools Nest exposes is #nestjs/testing. In short, you'll want to create a Custom Provider for the dependency you are looking to mock, and that's all there is. However, it's always better to see an example, so here is a possibility of a mock for the controller:
describe('UserController', () => {
let controller: UserController;
let service: UserService;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
controllers: [UserController],
providers: [
{
provide: UserService,
useValue: {
get: jest.fn(() => mockUserEntity) // really it can be anything, but the closer to your actual logic the better
}
}
]
}).compile();
controller = moduleRef.get(UserController);
service = moduleRef.get(UserService);
});
});
And from there you can go on and write your tests. This is pretty much the same set up for all tests using Nest's DI system, the only thing to be aware of is things like #InjectRepository() and #InjectModel() (Mongoose and Sequilize decorators) where you'll need to use getRepositoryToken() or getModelToken() for the injection token. If you're looking for more exmaples take a look at this repository

NestJS How to add custom Logger to custom ExceptionFilter

I am using NestJS 5.4.0
I have custom LoggerService, it's working perfectly. But, how can I add this LoggerService to ExceptionFilter.
// logger.service.ts
import {Injectable, LoggerService} from '#nestjs/common';
#Injectable()
export class Logger implements LoggerService {
log(message: string) {
console.log(message);
}
error(message: string, trace: string) {
console.error(message);
}
warn(message: string) {
console.warn(message);
}
}
//logger.module.ts
import { Module } from '#nestjs/common';
import {Logger} from '../services/logger.service';
#Module({
providers: [Logger],
exports: [Logger],
})
export class LoggerModule {}
// user.module.ts
import { Module } from '#nestjs/common';
import {UserService} from '../services/user.service';
import {LoggerModule} from './logger.module';
#Module({
imports: [LoggerModule],
providers: [UserService],
exports: [UserService],
})
export class UserModule {}
It's working perfectly.
import {Logger} from './logger.service';
export class UserService {
constructor(
private logger: Logger
) {}
private test = () => {
this.logger.log("test"); // log success "test" to console
}
}
But how can I add my custom Logger to ExceptionFilter
// forbidden.exception.filter.ts
import {HttpException, HttpStatus, Injectable} from '#nestjs/common';
#Injectable()
export class ForbiddenException extends HttpException {
constructor(message?: string) {
super(message || 'Forbidden', HttpStatus.FORBIDDEN);
// I want to add my custom logger here!
}
}
Thank for reading.
First of all your class ForbiddenException extends HttpException is not
what it calls ExceptionFilter. ExceptionFilter is
exceptions layer which is responsible for processing all unhandled exceptions across an application
docs
You provided exmaple when you are trying to inject it to your custom HttpException. But thats wrong. Your exception don't have to be responsible for logging. Thats what ExceptionFilter should be responsible for.
Anyway, for now (17 oct 2019) there is no example in official docs how to inject providers to ExceptionFilter.
You can pass it to constructor on init, but you should to get Logger instance before with app.get<T>(...) method.
For example I've changed code from exception-filters docs:
// HttpExceptionFilter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '#nestjs/common';
import { Request, Response } from 'express';
import {MyLogger} from '../MyLogger'
#Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
constructor(private readonly logger: MyLogger) {}
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
if (status >= 500) {
this.logger.error({ request, response });
}
response
.status(status)
.json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
and bootstrap.ts code:
// bootstrap.ts
const app = await NestFactory.create(MainModule, {
logger: false,
});
const logger = app.get<MyLogger>(MyLogger);
app.useLogger(logger);
app.useGlobalFilters(new HttpExceptionFilter(logger));
This technique can be used for all this INestApplication methods:
app.useGlobalFilters
app.useGlobalGuards
app.useGlobalInterceptors
app.useGlobalPipes
app.useLogger
app.useWebSocketAdapter
First of all, to use dependency injection with Exception filters you cannot register them using the useGlobalFilters() method:
const app = await NestFactory.create(MainModule, {
logger: false,
});
const logger = app.get<MyLogger>(MyLogger);
app.useLogger(logger);
//Remove this line
//app.useGlobalFilters(new HttpExceptionFilter(logger));
Next in your MainModule, add your custom exception filter as a provider (note: filters are automatically set as global no matter what module you add them to but as a best practice, add them to your top level module):
import { Module } from '#nestjs/common';
import { APP_FILTER } from '#nestjs/core';
import { LoggerModule } from './logger.module';
import { ForbiddenException } from './forbidden.exception.filter.ts';
#Module({
imports: [
LoggerModule //this is your logger module
],
providers: [
{
provide: APP_FILTER, //you have to use this custom provider
useClass: ForbiddenException //this is your custom exception filter
}
]
})
export class MainModule {}
Now you can inject the logger into your custom exception filter:
import {HttpException, HttpStatus, Injectable} from '#nestjs/common';
import { Logger } from './path/to/logger';
#Injectable()
export class ForbiddenException extends HttpException {
constructor(private logger: Logger) {}
catch(exception: HttpException, response) {
this.logger.log('test');
}
}
Pseudo code but I think you get the idea.

Resources