I am having a hard time figuring out the NestJS and PassportJS combination when it comes to the authentication/authorization process, and I am a type of developer who does not like magic when it comes to developing.
Issue
Basically, my goal is to understand how does AuthGuard knows about the Passport Strategy being implemented in the project, it could be Local Strategy, or any other, for example JWT Strategy. I have two modules AuthModule and UserModule and this is how the AuthService looks like:
#Injectable()
export class AuthService {
constructor(private usersService: UsersService){}
async validateUser(username: string, password: string): Promise<any> {
const user = await this.usersService.findOne(username);
if (user && user.password === password) {
const {password, ...result} = user
return result
}
return null
}
}
UserService:
import { Injectable } from '#nestjs/common';
export type User = any;
#Injectable()
export class UsersService {
private readonly users = [
{
userId: 1,
username: 'John Marston',
password: 'rdr1',
},
{
userId: 2,
username: 'Arthur Morgan',
password: 'rdr2',
},
]
async findOne(username: string): Promise<User | undefined> {
return this.users.find(user => user.username === username)
}
}
Passport
After installing the packages for Passport and NestJS, I imported PassportModule and implemented the LocalStrategy and also imported that strategy as a provider inside the AuthModule
LocalStrategy:
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private 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;
}
}
#Module({
imports: [UsersModule, PassportModule],
providers: [AuthService, LocalStrategy]
})
export class AuthModule {}
Login route
import { Controller, Post, Request, UseGuards } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Controller()
export class AppController {
#UseGuards(AuthGuard('local'))
#Post('login')
async login(#Request() req) {
return req.user;
}
}
I understand everything up until this part. I do also understand how we get the req.user object etc. but I do not understand how does the AuthGuard knows, that we implemented Passport Local Strategy. Does it look through the files (sorry if this is dumb to say) and finds where we imported the PassportModule and also where we implemented the LocalStrategy since that class extends the PassportStrategy class, but also important to say, imported from passport-local.
I do understand that AuthGuard is a special type of Guard, but I am not sure if I understand it properly.
I have a rather good article on this, but to put it on StackOverflow too:
Each Strategy from a passport-* package has a name property that is the name for the strategy. For passport-local that name is local. For passport-jwt, that name is 'jwt'. This doesn't always line up one to one, but each package should document what its name for passport is. This is the name that gets passed to passport.use() and passport.authenticate(). passport.use is called via some clever code in the PassportStrategy class from #nestjs/passport to register the strategy with passport and passport.authenticate is called inside of the AuthGuard() using either the global default set via the passport module's options or via the mixin's parameters, like local in your code sample.
Related
This is not a duplicate Q. Please don't mark this as that.
Following is not I want
import { EntityRepository, Repository } from "typeorm";
import { Test } from "./test.model";
import { Injectable } from "#nestjs/common";
#EntityRepository(Test)
export class TestRepository extends Repository<Test> {}
the #EntityRepository decorator is now deprecated.
I also don't want to make a fake repository like in here:
https://stackoverflow.com/a/73352265/5420070
Don't want this either as I've to extract manager from dataSource, I don't want this because I think this is not the best way.
export const UserRepository = dataSource.getRepository(User).extend({
// ^^^^^^^^^^ from where this came from
findByName(firstName: string, lastName: string) {
return this.createQueryBuilder("user")
.where("user.firstName = :firstName", { firstName })
.andWhere("user.lastName = :lastName", { lastName })
.getMany()
},
})
Found above in: https://orkhan.gitbook.io/typeorm/docs/custom-repository#how-to-create-custom-repository
I don't think this is in NestJS context.
What I want
Want to know right way to make custom repository in latest version of NestJS (v9) & TypeORM (v0.3). In #EntityRepository deprecation note, they said that need to extend the repo to create custom repo like someRepo.extend({}). I want to know how to do it in NestJS way
In order to achieve what you want, you could do something like the following.
This solution is inspired by the official NestJS docs related to this topic, with some customization.
Steps to achieve it:
Create your TypeOrm entity as usual, let's say UserEntity (user.entity.ts file)
Create a UserRepository class (user.repository.ts file)
Create a UserService class as usual (user.service.ts file)
Import the UserRepository into your UserService
Update UserModule in order to provide the UserRepository and needed UserEntity
Detailed implementation example
1. user.entity.ts file
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
#Entity()
export class UserEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
firstName: string;
#Column()
lastName: string;
#Column({ default: true })
isActive: boolean;
}
2. user.repository.ts file
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { UserEntity } from './user.entity';
export class UserRepository extends Repository<UserEntity> {
constructor(
#InjectRepository(UserEntity)
private userRepository: Repository<UserEntity>
) {
super(userRepository.target, userRepository.manager, userRepository.queryRunner);
}
// sample method for demo purposes
async findByEmail(email: string): Promise<UserEntity> {
return await this.userRepository.findOneBy({ email }); // could also be this.findOneBy({ email });, but depending on your IDE/TS settings, could warn that userRepository is not used though. Up to you to use either of the 2 methods
}
// your other custom methods in your repo...
}
3. & 4. user.service.ts file
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { UserRepository } from './user.repository';
import { UserEntity } from './user.entity';
#Injectable()
export class UserService {
constructor(
private readonly userRepository: UserRepository, // import as usual
) {}
findAll(): Promise<UserEntity[]> {
return this.userRepository.find();
}
// call your repo method
findOneByEmail(email: string): Promise<UserEntity> {
return this.userRepository.findByEmail({ email });
}
findOne(id: number): Promise<UserEntity> {
return this.userRepository.findOneBy({ id });
}
async remove(id: string): Promise<void> {
await this.userRepository.delete(id);
}
// your other custom methods in your service...
}
5. Updating UserModule (user.module.ts file)
import { Module } from '#nestjs/common';
import { TypeOrmModule } from '#nestjs/typeorm';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { UserEntity } from './user.entity';
#Module({
imports: [TypeOrmModule.forFeature([UserEntity])], // here we provide the TypeOrm support as usual, specifically for our UserEntity in this case
providers: [UserService, UserRepository], // here we provide our custom repo
controllers: [UserController],
exports: [UserService, UserRepository] // add this only if you use service and/or custom repo within another module/service
})
export class UserModule {}
With this in place, you should be able to import the UserModule in your AppModule and be able to both implement custom methods in the UserRepository and use them in the UserService. You should also be able to call the manager and queryRunnner of the custom repository.
Additional Note
If you need to directly call your UserRepository methods from within another module/service, then update UserModule to export the UserRepository
Hope it helps, don't hesitate to comment.
import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from "typeorm";
import { CustomBaseEntity } from "../core/custom-base.entity";//custom-made
#Entity({name: 'rcon_log', schema: 'dbo'})
export class LogEntity extends CustomBaseEntity{
------------your code-----------
}
I have a web api endpoint where the user signs in with email and password. It is extremely similar to this:
https://docs.nestjs.com/security/authentication
And in fact we built it on top of this base code.
Some of my actual code:
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(
private authService: AuthService,
private accountClient: AccountClientService
) {
super({ usernameField: 'email' });
}
async validate(
email: string,
password: string,
#Request() req
): Promise<Partial<IUser>> {
console.log('validation', email, password, 25, '///1');
const user = await this.authService.validateUser(email, password);
if (user === BlockedStatusEnum.Blocked) {
throw new ForbiddenException({
status: HttpStatus.FORBIDDEN,
error: 'User is blocked from accessing the site',
});
}
return user;
}
}
and later on in the Guard, here's some of what I'd like to happen:
#Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService
) {}
async validateUser(
email: string,
pass: string,
context: ExecutionContext // I know this doesn't work, but I'd like it to.
): Promise<Partial<UserEntity> | Promise<string>> {
const request = await context.switchToHttp().getRequest();
console.log(request.ip); // <------------------------
const user = await this.usersService.get(email);
if (user.isBlocked) {
throw new ForbiddenException();
}
// function continues ...
}
I am trying to use ExecutionContext but it ends up undefined.
I have also tried importing Request and sticking #Request() req in there but it is undefined as well.
I am using TypeORM by way of NestJS to connect to a PostgreSQL database. For every connection I make to the database, I need to 'SET ROLE my-service', preferably just once when the connection is established. What are my options?
I have tried to find a way using the config that's passed to TypeOrmModule.forRoot(). I've considered the possibility that I can issue instructions to the PostgreSQL client ahead of time, but I don't have a strong sense of how to do that.
Please scroll to Update 2, per your unpated question.
Option 1: As an injectable service
You can create a service SomeTypeOrmConfigService as an #Injectable(), and then inject it like so.
import { Injectable } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { TypeOrmModuleOptions, TypeOrmOptionsFactory } from '#nestjs/typeorm';
// create your injectable here
#Injectable()
export class SomeTypeOrmConfigService implements TypeOrmOptionsFactory {
constructor(private configService: ConfigService) {}
createTypeOrmOptions(): TypeOrmModuleOptions {
return {
type: 'mysql',
host: this.configService.get('DATABASE_HOST'),
username: this.configService.get('DATABASE_USERNAME'),
password: this.configService.get('DATABASE_PASSWORD'),
};
}
}
Option 2: Save the information in a common .env, or b) another approach database.provider.ts file and export that copied ans./credit to from here
import { ConfigModule, ConfigService } from '#nestjs/config';
import { TypeOrmModule } from '#nestjs/typeorm';
export const databaseProviders = [
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
type: 'postgres',
host: configService.get('PGHOST'),
port: +configService.get<number>('PGPORT'),
username: configService.get('PGUSER'),
password: configService.get('PGPASSWORD'),
database: configService.get('PGDATABASE'),
entities: ['dist/**/*.entity{.ts,.js}'],
synchronize: false,
logging: true,
}),
}),
];
Then import it in your database.module.ts
import { databaseProviders } from './database.provider';
import { DatabaseService } from './database.service';
import { Module } from '#nestjs/common';
#Module({
imports: [...databaseProviders],
providers: [DatabaseService],
exports: [...databaseProviders],
})
export class DatabaseModule {}
And that's all, you can add multiple connections in your database.provider.ts if you want it, also, donĀ“t forget to create the .env and import the database module in your root module.
Update Answer (V2) to updated question
In a nutshell.. I think you are missing a) session module like nestjs-session , once you use that you can optionally b) cache it in your role with your session object
Step 1: use nestjs-session
Step 2: Now you can have access/use role in the session property object of your request
Step 3: You can then make it an injectable or a user factory with the help of the NestSessionOptions
My humble recommendation, save yourself time, use a pre-rolled package as a decorator.. try this package from here - use their sample ref. below ->
npm i nestjs-roles
1. Create Your roles
// role.enum.ts
export enum Role {
ADMIN = 'ADMIN',
USER = 'USER',
MODERATOR = 'MODERATOR',
}
2. Let's say you use nestjs-session and keep role in session property object of request. So then create guard with getRole callback:
// roles.guard.ts
import { ExecutionContext } from '#nestjs/common';
import { createRolesGuard } from 'nestjs-roles';
import { Role } from './role.enum';
function getRole(context: ExecutionContext) {
const { session } = context.switchToHttp().getRequest();
if (!session) {
return;
}
return (session as { role?: Role }).role;
}
export const Roles = createRolesGuard<Role>(getRole);
3. After that we can set Roles guard globally (don't forget to pass Reflector instance):
// bootstrap.ts
import { NestFactory, Reflector } from '#nestjs/core';
import { Roles } from './roles.guard';
const app = await NestFactory.create(AppModule);
const reflector = app.get<Reflector>(Reflector);
app.useGlobalGuards(new Roles(reflector));
4. All settings are done. Now you can set up access in your controllers:
// secrets.controller.ts
import { Roles } from './roles.guard';
#Controller('secrets')
#Roles.Params(true) // setup access on Controller for users with any existing role
export class SecretsController {
#Get('my')
async readMy() {
// ...
}
#Patch(':id')
#Roles.Params(Role.ADMIN) // override access on certain handler
async update() {
// ...
}
}
I try to block some root if it's not an admin, but when I run the code I have a TypeError but I don't know how to resolve it.
Thanks
roles.guards.ts
import { Injectable, CanActivate, ExecutionContext } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
import { Role } from './role.enums';
import { ROLES_KEY } from './roles.decorator';
#Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some((Role) => user.roles?.includes(Role));
}
}
articles.controllers.ts
#UseGuards(JwtAuthGuard)
#Roles(Role.Admin)
async addArticle(
#Body('title') artTitle: string,
#Body('description') artDescription: string,
#Body('url') artUrl: string,
#Body('cover') artCover: string,
#Body('content') artContent: string,
#Body('category') artCategory: string,
){
const generatedId = await this.articlesService.insertArticle(
artTitle,
artDescription,
artUrl,
artCover,
artContent,
artCategory
);
return { id: generatedId };
}
when I run the code I have a TypeError but I don't know how to resolve it.
Thanks
I'd like to add more detail to Jay McDoniel's answer since it still took me a few hours to get around this issue.
Create JWT.module.ts (JwtModule is already used by #nestjs/jwt hence my use of caps) file with the following:
import { ConfigModule, ConfigService } from "#nestjs/config";
import { JwtModule } from "#nestjs/jwt";
#Module({
imports: [
{
...JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
secretOrKeyProvider: () => (configService.get<string>("JWT_SECRET")),
signOptions: {
expiresIn: 3600,
},
}),
inject: [ConfigService],
}),
global: true
}
],
exports: [JwtModule]
})
export class JWTModule {}
Add this class to your app.module.ts's imports array.
if you have
{
provide: APP_GUARD,
useClass: RolesGuard,
},
in any of your modules... DELETE IT. declaring guards in any providers will automatically make it global and endpoints which you don't want to be guarded will end up getting guarded (I'm aware https://docs.nestjs.com/security/authorization#basic-rbac-implementation tells you to register the role guard in your providers but that just didn't work for me). You only need to import your strategies to the relevant routes.
In your controller, this should now work
#ApiBearerAuth()
#Roles(Role.Admin)
#UseGuards(JwtAuthGuard, RolesGuard)
#Get()
findAll() {
return this.usersService.findAll();
}
so this endpoint accepts users with a valid JWT and an admin role inside said JWT.
If I had to bet, your RolesGuard is bound to the global scope, whereas the JwtAuthGuard is bound to the route handler scope. Global guards execute first and foremost, so the RolesGuard executes before the JwtAuthGaurd can set req.user (passport is what does this under the hood). What you can do is either ensure that there is a req.user property (either via a middleware or jutt running the JwtAuthGuard globally) or you can move the RolesGuard to be scoped at the route handler level after the JwtAuthGuard runs.
Use JwtGuard and RoleGuard in the controller like #UseGuards(JwtAuthGuard, RolesGuard). The issue because of RoleGuards is not used in guard.
#Roles(Role.ADMIN)
#UseGuards(JwtAuthGuard,RolesGuard)
#Query(() => [User], { name: 'User' })
articles.module.ts
in this module file update in provider rolesGuards
providers: [AuthResolver, AuthService,LocalStrategy,JwtStrategy,RolesGuard],
use #Post() above your controller
#UseGuards(JwtAuthGuard)
#Roles(Role.Admin)
#Post('')
async addArticle(
#Body('title') artTitle: string,
#Body('description') artDescription: string,
#Body('url') artUrl: string,
#Body('cover') artCover: string,
#Body('content') artContent: string,
#Body('category') artCategory: string,
){
const generatedId = await this.articlesService.insertArticle(
artTitle,
artDescription,
artUrl,
artCover,
artContent,
artCategory
);
return { id: generatedId };
}
Im new in NEST JS, and now im try to include some validator in DTO'S
looks like:
// /blog-backend/src/blog/dto/create-post.dto.ts
import { IsEmail, IsNotEmpty, IsDefined } from 'class-validator';
export class CreatePostDTO {
#IsDefined()
#IsNotEmpty()
title: string;
#IsDefined()
#IsNotEmpty()
description: string;
#IsDefined()
#IsNotEmpty()
body: string;
#IsEmail()
#IsNotEmpty()
author: string;
#IsDefined()
#IsNotEmpty()
datePosted: string;
}
But when i excute the post service like:
{
"title":"juanita"
}
Its return good!
But the validators should show and error rigth?
My post controloler
#Post('/post')
async addPost(#Res() res, #Body() createPostDTO: CreatePostDTO) {
console.log(createPostDTO)
const newPost = await this.blogService.addPost(createPostDTO);
return res.status(HttpStatus.OK).json({
message: 'Post has been submitted successfully!',
post: newPost,
});
}
My main.ts
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(5000);
}
bootstrap();
Let's binding ValidationPipe at the application level, thus ensuring all endpoints are protected from receiving incorrect data. Nestjs document
Enable ValidationPipe for your application.
main.ts
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '#nestjs/common'; // import built-in ValidationPipe
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe()); // enable ValidationPipe`
await app.listen(5000);
}
bootstrap();
For validating requests through the incoming dtos, you can use the #UsePipes() decorator provided by NestJS. This decorator can be applied globally (for the complete project), on individual endpoints. This is what the NestJS documentation says about it -
Pipes, similar to exception filters, can be method-scoped, controller-scoped, or global-scoped. Additionally, a pipe can be param-scoped. In the example below, we'll directly tie the pipe instance to the route param #Body() decorator.
Thus using it for your POST endpoint will help validate the request.
Hope this helps.
You also can register global pipes in app.module.ts file like this:
providers: [
{
provide: APP_PIPE,
useValue: new ValidationPipe({
// validation options
whitelist: true,
}),
},
],