英文:
How to achieve IP Rate Limiting along with Body Request Rate Limiting in NestJS With Redis?
问题
I was wondering if we can achieve Rate Limiting of IP and Request Body (same some username) separately (Either one if fulfilled should give me Error 429) for same controller function (Route).
Tried Using following packages -
"nestjs-throttler-storage-redis": "^0.3.0"
"@nestjs/throttler": "^4.0.0",
"ioredis": "^5.3.2"
app.module.ts -
ThrottlerModule.forRoot({
ttl: process.env.IP_VELOCITY_TTL as unknown as number, // 24 hours in seconds
limit: process.env.IP_VELOCITY_COUNT as unknown as number, // X number requests per ttl per key (IP address in this case)
storage: new ThrottlerStorageRedisService(new Redis()),
}),
In Respective Module.ts -
{
provide: APP_GUARD,
useClass: ThrottlerGuard,
},
controller.ts -
@Throttle(3, 60 * 60)
But this is not sufficient as this is blocking all the requests post 3 times!
Can anybody suggest me to achieve this in Right way ?
英文:
I was wondering if we can achieve Rate Limiting of IP and Request Body (same some username) separately (Either one if fulfilled should give me Error 429) for same controller function (Route).
Tried Using following packages -
"nestjs-throttler-storage-redis": "^0.3.0"
"@nestjs/throttler": "^4.0.0",
"ioredis": "^5.3.2"
app.module.ts -
ThrottlerModule.forRoot({
ttl: process.env.IP_VELOCITY_TTL as unknown as number, // 24 hours in seconds
limit: process.env.IP_VELOCITY_COUNT as unknown as number, // X number requests per ttl per key (IP address in this case)
storage: new ThrottlerStorageRedisService(new Redis()),
}),
In Respective Module.ts -
{
provide: APP_GUARD,
useClass: ThrottlerGuard,
},
controller.ts -
@Throttle(3, 60 * 60)
But this is not sufficient as this is blocking all the requests post 3 times!
Can anybody suggest me to achieve this in Right way ?
答案1
得分: 1
以下是翻译好的代码部分:
// 技巧是覆盖`ThrottlerGuard`类,如下所示 -
import { ExecutionContext, Injectable } from '@nestjs/common';
import { ThrottlerGuard } from '@nestjs/throttler';
@Injectable()
export class CustomThrottlerGuard extends ThrottlerGuard {
// 覆盖以处理IP限制以及firstName + lastName限制
async handleRequest(
context: ExecutionContext,
limit: number,
ttl: number
): Promise<boolean> {
const { req, res } = this.getRequestResponse(context);
// 如果应忽略当前用户代理,请提前返回。
if (Array.isArray(this.options.ignoreUserAgents)) {
for (const pattern of this.options.ignoreUserAgents) {
if (pattern.test(req.headers['user-agent'])) {
return true;
}
}
}
// IP跟踪器
const tracker = this.getTracker(req);
const key = this.generateKey(context, tracker);
const { totalHits, timeToExpire } = await this.storageService.increment(
key,
ttl
);
// firstName和lastName的跟踪器
const firstNameAndLastNameTracker = this.getNameTracker(req);
const nameKey = this.generateKey(context, firstNameAndLastNameTracker);
const { totalHits: totalHitsName, timeToExpire: timeToExpireName } =
await this.storageService.increment(nameKey, ttl);
// 当用户达到其限制(IP)时抛出错误。
if (totalHits > limit) {
res.header('Retry-After', timeToExpire);
this.throwThrottlingException(context);
}
// 当用户达到其firstName + lastName限制时抛出错误。
if (
totalHitsName > parseInt(process.env.FIRSTNAME_LASTNAME_MAX_TRY_COUNT)
) {
res.header('Retry-After', timeToExpireName);
this.throwThrottlingException(context);
}
res.header(`${this.headerPrefix}-Limit`, limit);
// 我们即将添加一条记录,所以我们需要在这里考虑这一点。
// 否则,标头会说我们还有一个请求,当没有请求时。
res.header(
`${this.headerPrefix}-Remaining`,
Math.max(0, limit - totalHits)
);
res.header(`${this.headerPrefix}-Reset`, timeToExpire);
return true;
}
protected getNameTracker(req: Record<string, any>): string {
return req.body.firstName + req.body.lastName;
}
}
请注意,我只翻译了代码部分,没有包括注释或其他内容。
英文:
The trick was to overwrite ThrottlerGuard
Class like below -
import { ExecutionContext, Injectable } from '@nestjs/common';
import { ThrottlerGuard } from '@nestjs/throttler';
@Injectable()
export class CustomThrottlerGuard extends ThrottlerGuard {
// Overwritten to handle the IP restriction along with firstName + lastName restriction
async handleRequest(
context: ExecutionContext,
limit: number,
ttl: number
): Promise<boolean> {
const { req, res } = this.getRequestResponse(context);
// Return early if the current user agent should be ignored.
if (Array.isArray(this.options.ignoreUserAgents)) {
for (const pattern of this.options.ignoreUserAgents) {
if (pattern.test(req.headers['user-agent'])) {
return true;
}
}
}
// Tracker for IP
const tracker = this.getTracker(req);
const key = this.generateKey(context, tracker);
const { totalHits, timeToExpire } = await this.storageService.increment(
key,
ttl
);
// Tracker for firstName and lastName
const firstNameAndLastNameTracker = this.getNameTracker(req);
const nameKey = this.generateKey(context, firstNameAndLastNameTracker);
const { totalHits: totalHitsName, timeToExpire: timeToExpireName } =
await this.storageService.increment(nameKey, ttl);
// Throw an error when the user reached their limit (IP).
if (totalHits > limit) {
res.header('Retry-After', timeToExpire);
this.throwThrottlingException(context);
}
// Throw an Error when user reached their firstName + lastName Limit.
if (
totalHitsName > parseInt(process.env.FIRSTNAME_LASTNAME_MAX_TRY_COUNT)
) {
res.header('Retry-After', timeToExpireName);
this.throwThrottlingException(context);
}
res.header(`${this.headerPrefix}-Limit`, limit);
// We're about to add a record so we need to take that into account here.
// Otherwise the header says we have a request left when there are none.
res.header(
`${this.headerPrefix}-Remaining`,
Math.max(0, limit - totalHits)
);
res.header(`${this.headerPrefix}-Reset`, timeToExpire);
return true;
}
protected getNameTracker(req: Record<string, any>): string {
return req.body.firstName + req.body.lastName;
}
}
答案2
得分: 0
You'll need to create your own guard that extends ThrottlerGuard
and overrides the getTracker
method so that it returns this ip
and req.body.username
combo. Something like
@Injectable()
export class ThrottleIpBodyGuard extends ThrottlerGuard {
getTracker(req: Request) {
return req.ip + req.body.username;
}
}
Then, instead of useClass: ThrottlerGuard
you can use useClass: ThrottleIpBodyGuard
英文:
You'll need to create your own guard that extends ThrottlerGuard
and overrides the getTracker
method so that it returns this ip
and req.body.username
combo. Something like
@Injectable()
export class ThrottleIpBodyGuard extends ThrottlerGuard {
getTracker(req: Request) {
return req.ip + req.body.username;
}
}
Then, instead of useClass: ThrottlerGuard
you can use useClass: ThrottleIpBodyGuard
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论