I follow this link https://docs.nestjs.com/security/authentication#enable-authentication-globally
I went ahead and create a public guard
But it does not recognize my requests when I put #Public on top of any method
and returns undefined !
I want to detect usser is logged on public routes
JwtAuthGuard
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) {
return true;
}
return super.canActivate(context);
}
}
Public Decorator
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
Sample controller
#Public()
#Get()
async find(#LoginUser() user: User): Promise<any> {
console.log(user);
// the user is undefined even after login
}
You set the route to be public and have a clause in your guard that says "if the route is public return true before attempting a login". If you want to login regardless of if the route is public or not, and then return ture at all times you can do something like
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
const canAct = await (super.canActivate(context) as Promise<boolean>)
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
return isPublic ?? canAct;
}
}
This will now have passport attempt a login from the get-go. So long as you don't throw an error in the valdiate method, or anything the validate method calls, everything should proceed as Passport does the login, check if the route is public, and return true if so, return if the login was successful if the route is not public.
write this:
constructor(#Inject(Reflector) private reflector: Reflector){
super();
}
Related
[SOLVED] I'm pretty new to NestJS and trying to get my head around durable providers but i can't get them to work.
My scenario is that i have a service with some logic and two providers that implement the same interface to get some data. Depending on a custom header value i want to use Provider1 or Provider2 and the service itself does not have to know about the existing provider implementations.
Since i'm in a request scoped scenario but i know there are only 2 possible dependency-subtrees i want to use durable providers that the dependencies are not newly initialised for each request but reused instead.
I set up the ContextIdStrategy as described in the official docs and it is executed on each request but i miss the part how to connect my provider implementations with the ContextSubtreeIds created in the ContextIdStrategy.
Interface:
export abstract class ITest {
abstract getData(): string;
}
Implementations:
export class Test1Provider implements ITest {
getData() {
return "TEST1";
}
}
export class Test2Provider implements ITest {
getData() {
return "TEST2";
}
}
Service:
#Injectable()
export class AppService {
constructor(private readonly testProvider: ITest) {}
getHello(): string {
return this.testProvider.getData();
}
}
Controller:
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Get()
getData(): string {
return this.appService.getData();
}
}
ContextIdStrategy:
const providers = new Map<string, ContextId>([
["provider1", ContextIdFactory.create()],
["provider2", ContextIdFactory.create()],
]);
export class AggregateByProviderContextIdStrategy implements ContextIdStrategy {
attach(contextId: ContextId, request: Request) {
const providerId = request.headers["x-provider-id"] as string;
let providerSubTreeId: ContextId;
if (providerId == "provider1") {
providerSubTreeId = providers["provider1"];
} else if (providerId == "provider2") {
providerSubTreeId = providers["provider2"];
} else {
throw Error(`x-provider-id ${providerId} not supported`);
}
// If tree is not durable, return the original "contextId" object
return (info: HostComponentInfo) =>
info.isTreeDurable ? providerSubTreeId : contextId;
}
}
Main:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
ContextIdFactory.apply(new AggregateByProviderContextIdStrategy());
await app.listen(3000);
}
bootstrap();
Module:
#Module({
imports: [],
controllers: [AppController],
providers: [
{
provide: ITest,
useFactory: () => {
// THIS IS THE MISSING PIECE.
// Return either Test1Provider or Test2Provider based on the ContextSubtreeId
// which is created by the ContextIdStrategy
return new Test1Provider();
},
},
AppService,
],
})
export class AppModule {}
The missing part was a modification of the ContextIdStrategy return statement:
return {
resolve: (info: HostComponentInfo) => {
const context = info.isTreeDurable ? providerSubTreeId : contextId;
return context;
},
payload: { providerId },
}
after that change, the request object can be injected in the module and where it will only contain the providerId property and based on that, the useFactory statement can return different implementations
I'm trying to package my own AuthGuard for use in other projects and need to pass it a string before use.
Because I saw the Passport auth guard use a function that wrapped around a new class I've done the same...
export const AnchorAuthGuard = (rpc?: string): Type<CanActivate> => {
class AuthGuard implements CanActivate {
rpc = rpc || "https://eos.greymass.com";
async canActivate(context: ExecutionContext): Promise<boolean> {
const [req] = context.getArgs();
const { body } = req as { body: ProofPayload };
const proof = IdentityProof.from(body.proof);
const client = new APIClient({
provider: new AxiosAPIProvider(this.rpc),
});
const accountName = proof.signer.actor;
const account = await client.v1.chain.get_account(accountName);
const auth = account.getPermission(proof.signer.permission).required_auth;
const valid = proof.verify(auth, account.head_block_time);
if (valid) {
req.anchor = {
account: {
actor: proof.signer.actor.toString(),
permission: proof.signer.permission.toString(),
},
object: account.toJSON(),
};
return true;
} else {
return false;
}
}
}
return AuthGuard;
};
However, now that I've packaged this up and extending the Guard with extends for some more functionality in a projhect I'm consuming the library in I'm having trouble figuring out how to enter the rpc parameter via configService from the ConfigModule and now feel like I'm not using the best practices here and that there's a better way from the start.
Any ideas?
I am not sure if I understood you correctly but to modify AuthGuard you must extend AuthGuard class and write over canActivate method.
#Injectable()
export class LoginGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector, private config: ConfigService) {
super();
}
canActivate(context: ExecutionContext) {
return super.canActivate(context); // this is necessary due to possibly returning `boolean | Promise<boolean> | Observable<boolean>
}
}
Does anybody know where I can see the full code of canActivate method in AuthGuard('jwt')? I realized that canActivate method calls JwtStrategy validate method by using console.log() like this:
// jwt.strategy.ts
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
private readonly configService: ConfigService,
private readonly usersService: UsersService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: true,
secretOrKey: configService.get<string>('JWT_SECRET'),
});
}
async validate(payload: any) {
try {
const user = await this.usersService.getUserById(payload.id);
// console.log is here
console.log(user);
return user;
} catch (e) {
console.log(e);
return null;
}
}
}
If I use the original canActivate method, console.log is called. I thought that JwtStrategy is a middleware so the validate method is called whenever there is a request. However, when I try to override canActivate method to add authorization, console.log in JwtStrategy validate method is not called:
// jwt-auth.guard.ts
import { ExecutionContext, Injectable } from '#nestjs/common';
import { GqlExecutionContext } from '#nestjs/graphql';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req;
}
canActivate(context: ExecutionContext): boolean {
try {
// Override: handle authorization
// return true or false
// Should JwtStrategy.validate(something) be called here?
} catch (e) {
console.log(e);
return false;
}
}
}
Then I tried to find the original code of AuthGuard('jwt') in order to understand its logic, but I was not able to. Any help would be appreciated, thanks!
Okay, so this is gonna be a very fun deep dive into this. Buckle up.
Middleware as express methods do still exist in NestJS; that said, this is not your normal middleware in the sense of Express middleware. As you'v mentioned AuthGuard()#canActivate() ends up calling the appropriate PassportStrategy. These strategies get registered here specifically on lines 40-41 where passport.use() gets called. This registers the passport strategy class's validate method to be used for passport.verify(). Most of the under the hood logic is very abstract and the context while reading it can be lost, so take your time and understand the concepts of classes, mixins (functions that return classes), and inheritance.
Line 51 of AuthGuard is where the passportFn originally gets created, and in this passportFn passport.authenticate gets called (which calls passport.verify under its hood) (reading through Passport's code is even more confusing, so I'll let you run that when you want).
If you want to add some extra logic to your canActivate() method you can end up calling super.canActivate(context) to call the original canActivate() method that ends up calling passport.authenticate() and thus <Strategy>#validate. That could look something like
#Injectable()
export class CustomAuthGuard extends AuthGuard('jwt') {
async canActivate(context: ExecutionContext): Promise<boolean> {
// custom logic can go here
const parentCanActivate = (await super.canActivate(context)) as boolean; // this is necessary due to possibly returning `boolean | Promise<boolean> | Observable<boolean>
// custom logic goes here too
return parentCanActivate && customCondition;
}
}
I'm trying to pass my service to an instance of a class that I pass to a method decorator.
Here's the service:
#Injectable()
export class EntryService {
constructor(
#InjectRepository(EntryEntity)
private readonly entryRepository: Repository<EntryEntity>,
#InjectRepository(ImageEntity)
private readonly imageRepository: Repository<ImageEntity>,
private readonly awsService: AwsService,
private readonly connection: Connection,
private readonly categoriesService: CategoriesService,
private readonly cacheService: CacheService,
private readonly usersService: UserService,
private readonly imagesService: ImagesService,
private readonly notificationService: NotificationsService,
) {}
#RecordEntryOperation(new CreateOperation(this))
public async create(createEntryDto: CreateEntryBodyDto): Promise<Entry> {
const queryRunner = this.connection.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
await queryRunner.commitTransaction();
// more code
} catch (err) {
await queryRunner.rollbackTransaction();
} finally {
await queryRunner.release();
}
}
}
The thing here is that I need to use EntryService inside that class I pass to the RecordEntryOperation decorator.
The decorator (not fully implemented yet):
export const RecordEntryOperation = (operation: Operation) => {
return (target: object, key: string | symbol, descriptor: PropertyDescriptor) => {
const original = descriptor.value;
descriptor.value = async function(...args: any[]) {
const response = await original.apply(this, args);
console.log(`operation.execute()`, await operation.execute());
return response;
};
};
};
The CreateOperation class looks like this (not fully implemented yet):
export class CreateOperation extends Operation {
constructor(public entryService: EntryService) { super(); }
public async execute(): Promise<any> {
return this.entryService.someEntryServiceOperation();
}
}
The error I'm getting reads as follows:
Argument of type 'typeof globalThis' is not assignable to parameter of type 'EntryService'.
Type 'typeof globalThis' is missing the following properties from type 'EntryService': entryRepository, imageRepository, awsService, and 53 more.
I don't fully understand what this error is about. I suspect that it means that the this passed to the CreateOperation class does not have all these dependencies injected into the service by the dependency injector.
I tried different things, but to no avail. Seems like I don't completely understand what is going on.
Any ideas?
What would be the right way to structure the code then?
The problem is the following line:
#RecordEntryOperation(new CreateOperation(this))
this does not refer to the instance of EntryService as you might expect, instead it refers to the globalThis (that this actually refers to the current module), thus the error. What you could do is to change your Operation-class a bit and pass the entryService to the execute method.
export class CreateOperation extends Operation {
constructor() { super(); }
public async execute(entryService: EntryService): Promise<any> {
return entryService.someEntryServiceOperation();
}
}
Then you can do the following in your decorator:
export const RecordEntryOperation = (OperationType: typeof CreateOperation) => {
return (target: object, key: string | symbol, descriptor: PropertyDescriptor) => {
const operation = new OperationType();
const original = descriptor.value;
descriptor.value = async function(...args: any[]) {
const response = await original.apply(this, args);
console.log(`operation.execute()`, await operation.execute(this));
return response;
};
};
};
Then use it with:
#RecordEntryOperation(CreateOperation)
public async create(createEntryDto: CreateEntryBodyDto): Promise<Entry> { .. }
Bookstore exercise. A Book can be assigned a single Genre
Trying to assign a new book with the genreId supplied in the DTO.
BookRepository
#EntityRepository(Book)
export class BookRepository extends Repository<Book> {
constructor(
private genreService: GenresService,
) {
super();
}
async createBook(createBookDto: CreateBookDto): Promise<Book> {
const genre = await this.genreService.getOne(createBookDto.genreId);
const newBook = this.create({
...createBookDto,
genre,
});
return newBook.save();
}
}
GenreService
#Injectable()
export class GenresService {
constructor(
#InjectRepository(GenreRepository) private readonly genreRepository: GenreRepository,
) {}
async getOne(id: number): Promise<Genre> {
return this.getById(id);
}
private async getById(id: number): Promise<Genre> {
const found = await this.genreRepository.findOne(id);
if (!found) {
throw new NotFoundException(`Genre with id ${id} not found.`);
}
return found;
}
}
BookRepository and GenreRepository are imported together by the BookstoreModule, like so:
imports: [
TypeOrmModule.forFeature([
GenreRepository,
BookRepository,
AuthorRepository,
]),
// ...etc
},
NestJS spits out the following error:
[ExceptionsHandler] this.genreService.getOne is not a function +1045981ms
TypeError: this.genreService.getOne is not a function
at BookRepository.createBook (/Users/artur/Code/Sandbox/books-nest/dist/bookstore/books/book.repository.js:21:47)
at BooksService.createBook (/Users/artur/Code/Sandbox/books-nest/dist/bookstore/books/books.service.js:29:36)
at BooksController.create (/Users/artur/Code/Sandbox/books-nest/dist/bookstore/books/books.controller.js:31:33)
Tried to inject GenreRepository into theBookRepository`
constructor(
#InjectRepository(GenreRepository) private genreRepository: GenreRepository,
){
super();
}
and .findOne() from there, the error was:
No metadata for "2" was found. +26884ms
EntityMetadataNotFound: No metadata for "2" was found.
(2 being the id for genreId)
Not sure if my approach to saving is correct. Maybe the idea of finding the genre from within BookRepository is wrong and it should be solved in a different way. If so, how?
To me the createBook method should be lying within the BookService, BookService needs to inject GenreService and then you should call the genreService.getOne(createBookDto.genreId) from within the BookService.
It makes more sense to let the services handle the business logic / orchestration of the data flow imo.
I'd suggest to give it a try try with the following code:
BookService
#Injectable()
export class BookService {
constructor(
#InjectRepository(BookRepository) private readonly bookRepository: BookRepository,
private genreService: GenreService // <= here you go, you inject the genreService into the bookService to take advantage of its methods
) {}
async getOne(id: number): Promise<Genre> {
return this.getById(id);
}
async createBook(createBookDto: CreateBookDto): Promise<Book> {
const genre = await this.genreService.getOne(createBookDto.genreId);
const newBook = this.create({
...createBookDto,
genre,
});
return newBook.save();
}
private async getById(id: number): Promise<Genre> {
const found = await this.bookRepository.findOne(id);
if (!found) {
throw new NotFoundException(`Book with id ${id} not found.`);
}
return found;
}
}
BookRepository
#EntityRepository(Book)
export class BookRepository extends Repository<Book> {}
GenresService
#Injectable()
export class GenresService {
constructor(
#InjectRepository(GenreRepository) private readonly genreRepository: GenreRepository,
) {}
async getOne(id: number): Promise<Genre> {
return this.getById(id);
}
private async getById(id: number): Promise<Genre> {
const found = await this.genreRepository.findOne(id);
if (!found) {
throw new NotFoundException(`Genre with id ${id} not found.`);
}
return found;
}
}
GenreRepository
#EntityRepository(Genre)
export class GenreRepository extends Repository<Genre> {}
Edit DTO comment
CreateBookDto
import { Exclude, Expose } from 'class-transformer';
import { IsNumber, IsString, IsDate } from 'class-validator';
#Exclude()
export class CreateBookDto {
#Expose()
#IsNumber()
genreId: number;
#Expose()
#IsString()
title: string;
#Expose()
#IsString()
author: string;
#Expose()
#IsDate()
date_of_publication: Date;
// rest of informations....
}
Note that we're using class-transformer along with class-validator in order to instantiate and validate a proper CreateBookDto. CreateBookDto can be instantiated at the controller level if you're using validation pipe
Let me know if it helps :)