For example: to apply one middleware to multiple routes we can use:
export class UserModule {
public configure(consumer: MiddlewaresConsumer) {
consumer.apply(AuthMiddleware).forRoutes(
{ path: '/users', method: RequestMethod.GET },
{ path: '/users/:id', method: RequestMethod.GET },
{ path: '/users/:id', method: RequestMethod.PUT },
{ path: '/users/:id', method: RequestMethod.DELETE },
);
}
}
I would like apply AuthGuard to multiple routes, ¿ what is the best practice ? thanks ...
Currenly I use one by one decorator inside controller function like this,
#Get()
#UseGuards(AuthGuard('jwt'))
async findAll(#Request() request): Promise<User[]> {
return await this.usersService.findAll();
}
but I'm looking for a masive implementation
You have three possible solutions to set guard:
apply to method (your example)
apply to controller
apply globally
Apply to controller:
#Controller('cats')
#UseGuards(RolesGuard)
export class CatsController {}
Apply guard globally:
const app = await NestFactory.create(ApplicationModule);
app.useGlobalGuards(new RolesGuard());
All examples from guards docs - https://docs.nestjs.com/guards
Related
I have a problem with using multiple ClientKafka in one service, here is my implementation:
#Controller()
export class ApiController implements OnModuleInit {
constructor(
#Inject("ACCOUNT_SERVICE") private readonly accountKafkaClient: ClientKafka,
#Inject("WORKSPACE_SERVICE") private readonly workspaceKafkaClient: ClientKafka
) { }
async onModuleInit() {
const requestPatterns = [
'topic'
];
requestPatterns.forEach((pattern) => {
this.accountKafkaClient.subscribeToResponseOf(`account.${pattern}`);
});
await this.accountKafkaClient.connect();
}
async onModuleDestroy() {
await this.accountKafkaClient.close();
}
#Get()
async sendMessage() {
const data = {
msg: "account.topic"
}
const kafkaResponse = this.accountKafkaClient.send<any>('account.topic', JSON.stringify(data));
const response = await firstValueFrom(kafkaResponse);
const kafkaResponse2 = this.workspaceKafkaClient.send<any>('workspace.topic', JSON.stringify(response )) //THIS IS NOT RUNNING, WORKSPACE_SERVICE NOT RECEIVE ANY MESSAGE
return await firstValueFrom(kafkaResponse2);
}
}
can someone tell me why workspaceKafkaClient is not sending any message to WORKSPACE_SERVICE microservice? I try with passing this client in onModule... functions like accountKafkaClient but it didn't help me,
here is also my settings in module:
#Module({
imports: [
ClientsModule.register([
{
name: 'ACCOUNT_SERVICE',
transport: Transport.KAFKA,
options: {
client: {
clientId: 'account_service',
brokers: ['localhost:29092'],
},
consumer: {
groupId: 'account-consumer',
},
},
},
{
name: 'WORKSPACE_SERVICE',
transport: Transport.KAFKA,
options: {
client: {
clientId: 'workspace_service',
brokers: ['localhost:29092'],
},
consumer: {
groupId: 'workspace-consumer',
},
},
},
]),
],
controllers: [ApiController],
providers: [
ApiService,
// KafkaProducerProvider,
],
})
export class ApiModule {}
thanks for any help!
You only need one producer client per application, but Kafka producers never immediately send data to brokers.
You need to flush them for that to happen, which is what await firstValueFrom(...) should do, but you've not shown that method.
Otherwise, you seem to be trying to get the reponse from one topic to send to another, which is what a consumer should be used for, rather than blocking on one producer request.
I'm trying out a event sourced NestJS application.
I'm stuck at the following point:
In my GamesModule I'm setting up the Eventstore connection to my stream.
In these options there are write and read functions which are called by the library to update/ read the stream's last checkpoint position.
I'd like to call my service methods that write and read to/from the database from these functions.
I've tried injecting the service in the constructor, but because the register function is and has to be a static method, I don't have access to whatever is injected in the constructor.
Is it possible to use a service or repository in the Dynamic module's options?
Service writing to and reading from DB:
#Injectable()
export class EventStoreStateService {
private readonly logger = new Logger(EventStoreStateService.name);
constructor(
#InjectRepository(EventStoreState)
private eventStoreStateRepository: Repository<EventStoreState>,
) {}
updateCheckpoint(stream: string, position: number) {
const updated = this.eventStoreStateRepository.update(
{ lastCheckpoint: position },
{ streamName: stream },
);
this.logger.log({ updated });
return updated;
}
getLastCheckpoint(stream: string) {
const last = this.eventStoreStateRepository.findOne({
where: { streamName: stream },
});
this.logger.log({ last });
return last;
}
}
The module where I setup the event-store connection. In the useFactory store.write(key: string, value: number) I'd like to call my service methods
#Module({
imports: [EventStoreStateModule],
})
export class GamesModule {
constructor(
// no access to this service in the static method
//
#Inject(EventStoreStateService)
private readonly eventStoreStateService: EventStoreStateService,
) {}
static register(): // updateCheckpoint: (key: string, value: number) => Promise<number>,
// getLastCheckpoint: (key: string) => Promise<number>,
DynamicModule {
return {
module: GamesModule,
imports: [
CqrsModule,
EventStoreModule.registerFeatureAsync({
type: 'event-store',
useFactory: async (...args) => {
console.log({ args });
return {
featureStreamName: '$ce-game',
type: 'event-store',
subscriptions: [
{
type: EventStoreSubscriptionType.CatchUp, // research various types
stream: '$ce-game',
resolveLinkTos: true,
},
],
eventHandlers: EventStoreInstanciators,
store: {
storeKey: 'game',
write: async (key: string, value: number) => {
// TODO: on every new event for stream x this function
// is called with the last position number
// problem: we need access to the service that connects
// to ORM, but it's a static method so no access to whatever
// is injected in the constructor
//
},
read: async (key: string) => {
// same as write function
//
},
clear: () => null,
},
};
},
}),
TypeOrmModule.forFeature([GameProjection]),
],
controllers: [GamesController],
providers: [
GamesResolver,
GamesService,
GamesRepository,
...CommandHandlers,
...EventHandlers,
],
};
}
}
Using this library for event-store connection: https://github.com/juicycleff/nestjs-event-store.
Key things to know in NestJs.
If you want to access service within the same module, make sure you inject service in your constructor like constructor( private readonly eventStoreStateService: EventStoreStateService){}
If you want to access service from another module then you have to export the service in .module.ts exports: [EventStoreStateService] and then also inject in service or controller where you want to use it.
Fastify has some really awesome json schema support. (Link)
However, I now want to use the schemas which I added with fastify.addSchema(..) inside my business logic as well. For example (pseudo code):
schema = fastify.getSchema("schema1")
if (schema.validate(data)) {
console.log("ok");
} else {
console.log("not ok");
}
How can I achieve that?
Right now, in Fastify, one route has a set of validation functions.
These functions exist only because you set them in the { schema: {} } route
configuration.
So, in the first place, if you don't set those schemas in a route, you will be not able to access them.
The getSchema function retrieves the schema object, not che compiled function.
The relation is not 1:1 because a validation function may use more schemas via the $ref keyword.
The only way to archive what you need is to monkey patch the internal Fastify (highly discouraged)
Or open a feature request to the project.
Here an example, and as you can see, you are limited to get the route's validation functions inside the route's context.
So, it is far from being a flexible usage.
const fastify = require('fastify')({ logger: true })
const {
kSchemaBody: bodySchema
} = require('fastify/lib/symbols')
fastify.post('/', {
schema: {
body: {
$id: '#schema1',
type: 'object',
properties: {
bar: { type: 'number' }
}
}
}
}, async (request, reply) => {
const schemaValidator = request.context[bodySchema]
const result = schemaValidator({ bar: 'not a number' })
if (result) {
return true
}
return schemaValidator.errors
})
fastify.inject({
method: 'POST',
url: '/',
payload: {
bar: 33
}
}, (err, res) => {
console.log(res.json())
})
In my NestJs app I have two entities: person.entity.ts and property.entity.ts, the two are connected with OneToMany relation. I have created DTOs for both person and property.
The owning side of the relation is defined in Person like this:
#JoinColumn()
#OneToMany(
(type) => PersonProperties,
(personProperties) => personProperties.person,
{ cascade: ['insert', 'update'] },
)
personProperties: PersonProperties[];
My Controller for posting new Person looks like this:
#Post()
async create(#Body() createPersonDto: CreatePersonDto) {
return this.personService.create(createPersonDto);
}
For validation I am using Global ValidationPipes as below:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
transformOptions: {
enableImplicitConversion: true,
},
}),
);
How should I modify my code so I can post (and validate) a Person with their properties with a single Request? Isn't this going to include one DTO inside another?
What you must use are Pipes Read more about it
In your case, a Validation Pipe is required to validate the incoming request against your DTO.
Pipes are used inside a controller, though you can read about creating custom pipes inbuilt ValidationPipe would satisfy your needs for now.
METHOD 1
#Post()
async create(#Body(ValidationPipe) createPersonDto: CreatePersonDto) {
return this.personService.create(createPersonDto);
}
METHOD 2
#Post()
#UsePipes(ValidationPipe)
async create(#Body() createPersonDto: CreatePersonDto) {
return this.personService.create(createPersonDto);
}
Both methods essentially do the same thing. It takes your Body request object and validates it against your defined DTO.
Hope this answers your question.
The solution is to create a composite DTO object as follows:
export class CreatePersonWithPropertiesDto {
#ValidateNested()
#Type(() => CreatePersonDto)
neuron: CreatePersonDto;
#IsOptional()
#ValidateNested({ each: true })
#Type(() => CreatePersonPropertiesDto)
personProperties?: CreatePersonPropertiesDto[];
}
and then adjust the controller to expect the composite DTO:
#Post()
async create(#Body() createPersonWithPropertiesDto: CreatePersonWithPropertiesDto) {
return this.personService.create(createPersonWithPropertiesDto);
}
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 };
}