#Throttler debugging help
58 messages · Page 1 of 1 (latest)
its not rate limiting me while im sending more than 4 requests per 2 mins but I cant debug anything like limit, tracker etc
like I can in my socket throttler
import { Injectable } from '@nestjs/common';
import { ThrottlerGuard, ThrottlerRequest } from '@nestjs/throttler';
@Injectable()
export class WsThrottlerGuard extends ThrottlerGuard {
async handleRequest(requestProps: ThrottlerRequest): Promise<boolean> {
const {
context,
limit,
ttl,
throttler,
blockDuration,
getTracker,
generateKey,
} = requestProps;
const client = context.switchToWs().getClient();
const tracker =
client.handshake.headers['x-forwarded-for']
?.toString()
.split(',')[0]
.trim() ||
client.handshake.headers['x-real-ip'] ||
client.handshake.address;
const key = generateKey(context, tracker, throttler.name);
const { totalHits, timeToExpire, isBlocked, timeToBlockExpire } =
await this.storageService.increment(
key,
ttl,
limit,
blockDuration,
throttler.name,
);
console.log(ttl, limit, tracker, totalHits, limit, isBlocked);
I can debug here
but not in here
@Module({
imports: [
ThrottlerModule.forRoot([
{
ttl: 1200000,
limit: 4,
},
]),```
like it works when using insomnia or whatever but my website it just keeps going 
never ratelimit
fixed
how do I allow certain IPs to bypass ratelimit?
like the vps ip itself
so my discord bot can send requests without getting ratelimited
Bump
There is the skipIf function that can return a boolean and skip the throttling if true
Where do I apply that logic tho?
in the custom GetTracker?
or smth
In the throttler module's options
bump
Have you looked at the interface of the module's options?
yeah the docs are so lame on throttler
it doesnt mention anything useful for making ur own custom one
😭
I want to change the handling of generatekey
to uh
basically have 3 options
authorized,
unauthorized
websocket
authorized = a HTTP request made with either a header ratelimit-discordid or a cookie called SESSION (signed cookieparser) and then valid (get user from that session or find user with that discordid header) in database else unauthorized
websocket = a socketio request made with handshake.auth.sessionId signed like the above cookie so then we use cookieparser.signedcookie and we get result find in database with that if not unauthorized)
websocket = like 100 req per min (itll be per user so like key websocket:discordid
authorized = like 100 req per min (itll be per user so like key authorized:discordid
unauthorized like 500 req per min (itll be global to every unauthorized user/request from website or discord bot so like key unauthorized:ip?) (ip should always be internal since theres middleware checking for internal ip else reject request)
cuz I want seperate ratelimits for my website requests, discord bot requests and global unauthorized user ones
but uh
yeah it aint working 😭
import { Injectable, ExecutionContext } from '@nestjs/common';
import { ThrottlerGuard } from '@nestjs/throttler';
import * as cookieParser from 'cookie-parser';
@Injectable()
export class CustomThrottlerGuard extends ThrottlerGuard {
protected async getTracker(req: Record<string, any>): Promise<string> {
return (
req.headers['x-forwarded-for']?.toString().split(',')[0].trim() || req.ip
);
}```
protected getRequestFromContext(context: ExecutionContext) {
console.log('\n[CustomThrottlerGuard] Getting request context...');
if (context.getType() === 'ws') {
const client = context.switchToWs().getClient();
const sessionId = cookieParser.signedCookie(
client.handshake.auth.sessionID || client.handshake.headers.sessionID,
process.env.COOKIE_SECRET,
);
return {
ip:
client.handshake.headers['x-forwarded-for']
?.toString()
.split(',')[0]
.trim() || client.handshake.address,
sessionId: sessionId?.toString(),
discordId:
client.handshake.auth.discordId || client.handshake.headers.discordId,
cookies: client.handshake.headers.cookie,
headers: client.handshake.headers,
};
}
console.log('[CustomThrottlerGuard] HTTP request detected');
const request = context.switchToHttp().getRequest();
if (!request || !request.headers) {
throw new Error('Invalid HTTP request');
}
const discordId = request.headers['ratelimit-discordid'];
const { SESSION } = request.cookies;
let sessionId = null;
if (SESSION && process.env.COOKIE_SECRET) {
sessionId = cookieParser.signedCookie(SESSION, process.env.COOKIE_SECRET);
}
if (!request.path || !request.method) {
throw new Error('Request missing path or method');
}
console.log({
...request,
discordId: discordId || null,
sessionId: sessionId?.toString() || null,
ip:
request.headers['x-forwarded-for']?.toString().split(',')[0].trim() ||
request.ip,
});
return {
...request,
discordId: discordId || null,
sessionId: sessionId?.toString() || null,
ip:
request.headers['x-forwarded-for']?.toString().split(',')[0].trim() ||
request.ip,
};
}
protected generateKey(
context: ExecutionContext,
suffix: string,
name: string,
): string {
const request = this.getRequestFromContext(context);
let throttleName: string;
console.log('[CustomThrottlerGuard] Request:', {
type: context.getType(),
discordId: request?.discordId,
sessionId: request?.sessionId,
ip: request?.ip,
});
if (context.getType() === 'ws') {
throttleName = 'websocket';
console.log('[CustomThrottlerGuard] Using websocket throttle');
} else {
throttleName =
request?.discordId || request?.sessionId
? 'authorized'
: 'unauthorized';
console.log('[CustomThrottlerGuard] Using throttle:', throttleName);
}
let key: string;
if (context.getType() === 'ws') {
key = request?.discordId
? `${throttleName}:ws:authorized:${request.discordId}`
: `${throttleName}:ws:unauthorized:${request.ip}`;
} else if (request?.discordId) {
key = `${throttleName}:authorized:discord:${request.discordId}`;
} else if (request?.sessionId) {
key = `${throttleName}:authorized:web:${request.sessionId}`;
} else {
key = `${throttleName}:unauthorized:${request.ip}`;
}
console.log('[CustomThrottlerGuard] Generated rate limit key:', {
key,
throttleName,
ip: request?.ip,
});
return key;
}
cuz I want seperate ratelimits for my website requests, discord bot requests and global unauthorized user ones
but uh
yeah it aint working 😭
yeah so basically uh
it doesnt check if discordid or sessionid is valid yet but that should be ez once I get the main key stuff working but uh
but uh it doesnt 
I fire without discordid or sessionid a http request and its unauthorized so that works ig
but
if I fire with discordid header
[CustomThrottlerGuard] Request: {
type: 'http',
discordId: '1045011641940574208',
sessionId: null,
ip: '::1'
}
[CustomThrottlerGuard] Using throttle: authorized
[CustomThrottlerGuard] Generated rate limit key: {
key: 'authorized:authorized:discord:1045011641940574208',
throttleName: 'authorized',
ip: '::1'
}```
it just keeps doing that
it never limits me to 3 requests

@Module({
imports: [
ThrottlerModule.forRoot([
{
name: 'authorized',
ttl: 60000, // 1 minute
limit: 3, // 3 requests per minute for authorized users
},
{
name: 'unauthorized',
ttl: 60000,
limit: 1, // 1 request per minute for unauthorized users
},
{
name: 'websocket',
ttl: 60000,
limit: 3, // 3 events per minute for websocket connections
},
]),
],
providers: [
{
provide: APP_GUARD,
useClass: CustomThrottlerGuard,
}
],
I know its a lot of code but I tried so many stuff I cant get it to work
I dont
Glad you finally wrote out the requirements of what you're trying to do rather than just saying you can't get it to work. Certainly will be a more involved configuration, but I believe it can be done, and if not yet it'll be supported in the future with updates on weighting requests
So, rather than a custom generateKey why not make use of the skipIf option for each of the named throttlers, to skip if certain conditions are not met (such as not an authorized HTTP request, not a request from a websocket, etc)
e.g.
@Module({
imports: [
ThrottlerModule.forRoot([
{
name: 'authorized',
ttl: 60000, // 1 minute
limit: 3, // 3 requests per minute for authorized users
skipIf: (ctx) => {
if (ctx.getType() !== 'http') {
return true;
}
const req = ctx.switchToHttp().getRequest()
return !req.discordId && !req.sessionId
}
},
{
name: 'unauthorized',
ttl: 60000,
limit: 1, // 1 request per minute for unauthorized users
skipIf: (ctx) => {
if (ctx.getType() !== 'http') {
return true;
}
const req = ctx.switchToHttp().getRequest()
return req.discordId || req.sessionId
}
},
{
name: 'websocket',
ttl: 60000,
limit: 3, // 3 events per minute for websocket connections
skipIf: (ctx: ExecutionContext) => ctx.getType() !== 'ws'
},
]),
],
providers: [
{
provide: APP_GUARD,
useClass: CustomThrottlerGuard,
}
],
Yeah my bad sorry
okay yeah this could actually work but how would I use a service or database (mongoose schema) in the skipIf
is that possible?
so I can do like
What's the use case for that?
to check if valid user/session or not
I'd probably delegate that to a separate guard, that works before this one. Try to keep responsibilities of the throttling package low
What about middleware
on every route
like I already do to check if IP is internal
That could work too
it works