We use ThrottlerGuard in our NestJS application from #nestjs/throttler package to rate limit connections and it's implemented like this:
#Injectable()
export class RateLimiter extends ThrottlerGuard {
RATE_LIMIT_EXCEEDED = 'Rate limit exceeded.';
async handleRequest(context: ExecutionContext, limit: number, ttl: number) {
const wsData = context.switchToWs().getData();
const metadata: MetadataObject = wsData.internalRepr;
const clientTokenMetadata = metadata.get(TOKEN_NAME) as MetadataValue[];
const clientToken = clientTokenMetadata[0].toString();
const tokensArray = await this.storageService.getRecord(clientToken);
if (tokensArray.length >= limit) {
throw new RpcException({
code: Status.UNAVAILABLE,
message: this.RATE_LIMIT_EXCEEDED,
});
}
await this.storageService.addRecord(clientToken, ttl);
return true;
}
}
Now, I need to inject another service, let's say ConfigService with the usage of constructor, but because it is an extended class, I need to call super(); method which required three more original arguments inherited from ThrottlerGuard, but they are not exported from the package. Is there any other way to make it work besides the method with DI in constructor?
What you can do, instead of extending ThrottleGuard is injecting it in your constructor and call canActivate() in handleRequest(). Benefit when not extending you don'
I haven't tested this but you can try the following:
import {ExecutionContext, Injectable} from "#nestjs/common";
import {ConfigService} from "#nestjs/config";
import {InjectThrottlerStorage, ThrottlerGuard, ThrottlerStorage} from '#nestjs/throttler'
#Injectable()
export class RateLimiter {
constructor(
private throttle: ThrottlerGuard,
private configService: ConfigService,
#InjectThrottlerStorage() private storage: ThrottlerStorage) {
}
async handleRequest(context: ExecutionContext, limit: number, ttl: number) {
// TODO something with ConfigService
return this.throttle.canActivate(context);
}
}
ThrottleGuard source code
Related
I have a few decorators that when called, I want to setMetadata to use it in my logging,
In my controller, I have these:
#Post("somePath")
#Permission("somePermission")
#UseGuards(JwtAuthGuard)
#HttpCode(200)
#Grafana(
"endpoint",
"functionalmap"
)
async getSubscriptionFromPwebFormFilter(
#Body(ValidationPipe) someDto: someDtoType
): Promise<ISuccessResponse> {
// some logic
}
In my decorators I want to set some data into the metadata to use in my logging inteceptor,
Grafana decorator:
export const Grafana = (functionalMap: string, endpoint: string) =>
applyDecorators(
SetMetadata("endpoint", endpoint),
SetMetadata("functionalMap", functionalMap)
);
AuthGuard decorator:
#Injectable()
export class JwtAuthGuard extends AuthGuard("jwt") {
constructor(
private readonly reflector: Reflector,
private readonly someService: SomeService
) {
super();
}
public async canActivate(context: ExecutionContext): Promise<boolean> {
const role = this.reflector.get<string>("permission", context.getHandler());
const request = context.switchToHttp().getRequest();
const { valueToLog } = request.body;
const jwtToken = request.headers.authorization;
console.log("check value exist", valueToLog);
SetMetadata("valueToLog", valueToLog);
}
Now, in my logging interceptor, I am getting all the values of the metadata this way:
#Injectable()
export default class LoggingInterceptor {
constructor(private readonly reflector: Reflector) {}
intercept(context: ExecutionContext, next: CallHandler) {
const executionStart = Date.now();
return next.handle().pipe(
tap((responseData) => {
const response = context.switchToHttp().getResponse<ServerResponse>();
const { statusCode } = response;
const valueToLog = this.reflector.get(
"valueToLog",
context.getHandler()
); // this is undefined
const endpoint = this.reflector.get("endpoint", context.getHandler()); // this have value
const functionalMap = this.reflector.get(
"functionalMap",
context.getHandler()
); // this have value
...
// some logic
})
);
}
}
In my case, the value of endpoint and functionalMap can be retrieved from the reflector, however, valueToLog is appearing as undefined,
Is setting of metadata not working for auth guard decorator?
I will be using the term "Decorator" in this explanation so I will first define it.
Decorators are functions that accept information about the decorated declaration.
Ex:
function ClassDecorator(target: typeof DecoratedClass) {
// do stuff
}
// it can then be used like this
#ClassDecorator
class DecoratedClass {}
when calling #SetMetadata(key, value) it returns a decorator. Functions like SetMetadata are referred to as Decorator Factories.
Decorators use the form #expression, where expression must evaluate to a function that will be called at runtime with information about the decorated declaration.
In your example you called SetMetadata("valueToLog", valueToLog). This returns a decorator. Decorators must be called with information about the decorated declaration. To actually attach the metadata you can do something like this:
SetMetadata("valueToLog", valueToLog)(context.getHandler());
TL;DR
I am trying to run mongoose query in my validator
Hello, I am trying to make a custom decorator which throws an error if a value for that field already exists. I am trying to use the mongoose model inside the class that validates the route. Unlike in resolver/controller, #InjectModel() does not work in validator class. My validator is like this
import { getModelToken, InjectModel } from "#nestjs/mongoose";
import {
ValidationArguments,
ValidatorConstraint,
ValidatorConstraintInterface,
} from "class-validator";
import { Model } from "mongoose";
import { User } from "../schema/user.schema";
#ValidatorConstraint({ name: "IsUniqueUser", async: true })
export class UniqueValidator implements ValidatorConstraintInterface {
constructor(
#InjectModel(User.name)
private readonly userModel: Model<User>,
) {}
async validate(value: any, args: ValidationArguments) {
const filter = {};
console.log(this.userModel);
console.log(getModelToken(User.name));
filter[args.property] = value;
const count = await this.userModel.count(filter);
return !count;
}
defaultMessage(args: ValidationArguments) {
return "$(value) is already taken";
}
}
and my DTO that uses the above decorator is
#InputType({})
export class UserCreateDTO {
#IsString()
name: string;
#IsUniqueUser({
message: "Phone number is already taken",
})
#Field(() => String)
phone: string;
}
The console says
cannot read value count of undefined implying that userModel is undefined.
InShort
I want to run the query in my validator. How can I do so?
According to this issue (you can't inject a dependency)
You should to add in your main.ts
import { useContainer } from 'class-validator';
useContainer(app.select(AppModule), {fallbackOnErrors: true});
Then you need to add your UniqueValidator to your module like an #Injectable() class
so
...
providers: [UniqueValidator],
...
Then, in your DTO you can add:
#Validate(UniqueValidator, ['email'], {
message: 'emailAlreadyExists',
})
I am trying to make use of the AuthGuard decorator, and the passport JWT strategy, following the documentation.
Everything in the documentation works great. But I now want to protect a route with a scope contained in the JWT. So here is a basic jwt payload generated by my application:
{
"user": {
"id": "20189c4f-1183-4216-8b48-333ddb825de8",
"username": "user.test#gmail.com"
},
"scope": [
"manage_server"
],
"iat": 1534766258,
"exp": 1534771258,
"iss": "15f2463d-8810-44f9-a908-801872ded159",
"sub": "20189c4f-1183-4216-8b48-333ddb825de8",
"jti": "078047bc-fc1f-4c35-8abe-72834f7bcc44"
}
Here is the basic protected route being guarded by the AuthGuard decorator:
#Get('protected')
#UseGuards(AuthGuard('jwt'))
async protected(): Promise<string> {
return 'Hello Protected World';
}
I would like to add options and restrict the access of that route to the people having the manager_server scope into their JWT. So after reading a little bit of the AuthGuard code, I thought that I was able to write something like:
#Get('protected')
#UseGuards(AuthGuard('jwt', {
scope: 'manage_server'
}))
async protected(): Promise<string> {
return 'Hello Protected World';
}
However, I can't see in the documentation where I could make use of this option.
I thought that adding an option argument to the validate function of the JWTStrategy could make the trick, but it does not. Here is my validate function (contained in the jwt.strategy.ts file):
async validate(payload: JwtPayload, done: ((err: any, value: any) => void)) {
const user = await this.authService.validateUser(payload);
if (!user) {
return done(new UnauthorizedException(), false);
}
done(null, user);
}
Thank you very much for your help and don't hesitate to ask me for more informations in the comments if you need so.
When you look at the code of the AuthGuard, it seems like the options.callback function is the only possible customization.
I think instead of writing your own AuthGuard that supports scope checks, it is cleaner to have a ScopesGuard (or RolesGuard) with its own decorater like #Scopes('manage_server') instead. For this, you can just follow the RolesGuard example in the docs, which also just checks an attribute of the JWT payload under the user property in the request.
Essential steps
Create a #Scopes() decorator:
export const Scopes = (...scopes: string[]) => SetMetadata('scopes', scopes);
Create a ScopesGuard:
#Injectable()
export class ScopesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const scopes = this.reflector.get<string[]>('scopes', context.getHandler());
if (!scopes) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
const hasScope = () => user.scopes.some((scope) => scopes.includes(scope));
return user && user.scopes && hasScope();
}
}
Use the ScopesGuard as a global guard for all routes (returns true when no scopes are given):
#Module({
providers: [
{
provide: APP_GUARD,
useClass: ScopesGuard,
},
],
})
export class ApplicationModule {}
And then use it on an endpoint:
#Get('protected')
#UseGuards(AuthGuard('jwt'))
#Scopes('manage_server')
async protected(): Promise<string> {
return 'Hello Protected World';
}
I tried a slightly different approach, by extending the AuthGuard guard. I wanted to maintain the ability to use different Passport Strategies, so I included a mixin. Feedback is appreciated.
In your Jwt strategy you could simply return the JwtPaylozd so that the user has a scopes attribute. Then the custom AuthGuard looks like this:
import { UnauthorizedException, mixin } from "#nestjs/common";
import { AuthGuard } from "#nestjs/passport";
export function AuthScopes(scopes: string[], type?: string | string[]) {
return mixin(class ScopesAuth extends AuthGuard(type) {
protected readonly scopes = scopes;
handleRequest(err, user, info, context) {
if (err || !user) {
throw err || new UnauthorizedException();
}
if(!this.scopes.some(s => user.scopes.split(' ').includes(s)))
{
throw new UnauthorizedException(`JWT does not possess one of the required scopes (${this.scopes.join(',')})`);
}
return user;
}
});
}
You can then use this guard like so:
#Get('protected')
#UseGuards(AuthScopes(['secret:read'], 'jwt'))
async protected(): Promise<string> {
return 'Hello Protected World';
}
'jwt' represents the strategy.
Consider the following code:
import redis = require('redis'); //Has ambient declaration from DT
import bluebird = require('bluebird'); //Has ambient declaration from DT
bluebird.promisifyAll((<any>redis).RedisClient.prototype);
bluebird.promisifyAll((<any>redis).Multi.prototype);
const client = redis.createClient();
client.getAsync('foo').then(function(res) {
console.log(res);
});
getAsync will error out because it's created on the fly and not defined in any .d.ts file. So what is the proper way to handle this?
Also, even though I have the .d.ts files loaded for redis, I still need to cast redis to any to be used for promisifyAll. Otherwise, it will spill out error:
Property 'RedisClient' does not exist on type 'typeof "redis"'
Is typing it to any the only easy way to go?
I'm solving this by declaration merging the setAsync & getAsync methods. I added the following code in my own custom .d.ts file.
declare module "redis" {
export interface RedisClient extends NodeJS.EventEmitter {
setAsync(key:string, value:string): Promise<void>;
getAsync(key:string): Promise<string>;
}
}
Another way to do it which requires less code is to extend the Redis object like so:
import { promisify } from 'util';
import { ClientOpts, RedisClient } from 'redis';
class AsyncRedis extends RedisClient {
public readonly getAsync = promisify(this.get).bind(this);
public readonly setAsync = promisify(this.set).bind(this);
public readonly quitAsync = promisify(this.quit).bind(this);
public readonly rpushAsync: (list: string, item: string) => Promise<number> = promisify(
this.rpush
).bind(this);
public readonly blpopAsync: (
list: string,
timeout: number
) => Promise<[string, string]> = promisify(this.blpop).bind(this);
public readonly flushdbAsync = promisify(this.flushdb).bind(this);
}
Notice that not all method signatures overwrite correctly, so you have to help typescript a little.
Now you can just use this enhanced class by creating it with your options, for example:
new AsyncRedis({
host: process.env.REDIS_HOST || '127.0.0.1',
password: process.env.REDIS_PASSWORD || 'whatever',
});
Just adding to Dave's answer, in my needs, I has to add in Multi for atomic operations.
declare module 'redis' {
export interface RedisClient extends NodeJS.EventEmitter {
execAsync(...args: any[]): Promise<any>;
hgetallAsync(...args: any[]): Promise<any>;
// add other methods here
}
export interface Multi extends Commands<Multi> {
execAsync(...args: any[]): Promise<any>;
// add other methods here
}
}
This solution works fine for me:
import { promisifyAll } from 'bluebird'; // import here works only if #types/bluebird is installed
import redis, { RedisClient, Multi } from 'redis'; // import here works only if #types/redis is installed
// Convert Redis client API to use promises, to make it usable with async/await syntax
const MultiAsync: any = promisifyAll(Multi.prototype);
const RedisClientAsync: any = promisifyAll(RedisClient.prototype);
const redisAsync = { ...redis, Multi: MultiAsync, RedisClient: RedisClientAsync };
const client: typeof RedisClientAsync = redisAsync.createClient();
// now you can use client async methods, i.e. client.getAsync, client.hgetAsync, client.hsetAsync, client.expireAsync...
Consider the following code:
import redis = require('redis'); //Has ambient declaration from DT
import bluebird = require('bluebird'); //Has ambient declaration from DT
bluebird.promisifyAll((<any>redis).RedisClient.prototype);
bluebird.promisifyAll((<any>redis).Multi.prototype);
const client = redis.createClient();
client.getAsync('foo').then(function(res) {
console.log(res);
});
getAsync will error out because it's created on the fly and not defined in any .d.ts file. So what is the proper way to handle this?
Also, even though I have the .d.ts files loaded for redis, I still need to cast redis to any to be used for promisifyAll. Otherwise, it will spill out error:
Property 'RedisClient' does not exist on type 'typeof "redis"'
Is typing it to any the only easy way to go?
I'm solving this by declaration merging the setAsync & getAsync methods. I added the following code in my own custom .d.ts file.
declare module "redis" {
export interface RedisClient extends NodeJS.EventEmitter {
setAsync(key:string, value:string): Promise<void>;
getAsync(key:string): Promise<string>;
}
}
Another way to do it which requires less code is to extend the Redis object like so:
import { promisify } from 'util';
import { ClientOpts, RedisClient } from 'redis';
class AsyncRedis extends RedisClient {
public readonly getAsync = promisify(this.get).bind(this);
public readonly setAsync = promisify(this.set).bind(this);
public readonly quitAsync = promisify(this.quit).bind(this);
public readonly rpushAsync: (list: string, item: string) => Promise<number> = promisify(
this.rpush
).bind(this);
public readonly blpopAsync: (
list: string,
timeout: number
) => Promise<[string, string]> = promisify(this.blpop).bind(this);
public readonly flushdbAsync = promisify(this.flushdb).bind(this);
}
Notice that not all method signatures overwrite correctly, so you have to help typescript a little.
Now you can just use this enhanced class by creating it with your options, for example:
new AsyncRedis({
host: process.env.REDIS_HOST || '127.0.0.1',
password: process.env.REDIS_PASSWORD || 'whatever',
});
Just adding to Dave's answer, in my needs, I has to add in Multi for atomic operations.
declare module 'redis' {
export interface RedisClient extends NodeJS.EventEmitter {
execAsync(...args: any[]): Promise<any>;
hgetallAsync(...args: any[]): Promise<any>;
// add other methods here
}
export interface Multi extends Commands<Multi> {
execAsync(...args: any[]): Promise<any>;
// add other methods here
}
}
This solution works fine for me:
import { promisifyAll } from 'bluebird'; // import here works only if #types/bluebird is installed
import redis, { RedisClient, Multi } from 'redis'; // import here works only if #types/redis is installed
// Convert Redis client API to use promises, to make it usable with async/await syntax
const MultiAsync: any = promisifyAll(Multi.prototype);
const RedisClientAsync: any = promisifyAll(RedisClient.prototype);
const redisAsync = { ...redis, Multi: MultiAsync, RedisClient: RedisClientAsync };
const client: typeof RedisClientAsync = redisAsync.createClient();
// now you can use client async methods, i.e. client.getAsync, client.hgetAsync, client.hsetAsync, client.expireAsync...