#Throttler debugging help

58 messages · Page 1 of 1 (latest)

bitter jetty
#

I want to debug the throttler to print the limit and ip cuz it isnt working rn

#

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 catdespair

#

never ratelimit

bitter jetty
#

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

bitter jetty
#

Bump

static zinc
bitter jetty
#

in the custom GetTracker?

#

or smth

static zinc
#

In the throttler module's options

bitter jetty
#

wat the

#

how do I do that

bitter jetty
#

bump

static zinc
#

Have you looked at the interface of the module's options?

bitter jetty
#

yeah the docs are so lame on throttler

#

it doesnt mention anything useful for making ur own custom one

#

😭

static zinc
bitter jetty
#

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 catdespair
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

static zinc
#

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

static zinc
#

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,
    }
  ],
bitter jetty
#

is that possible?

#

so I can do like

static zinc
#

What's the use case for that?

bitter jetty
#

to check if valid user/session or not

static zinc
#

I'd probably delegate that to a separate guard, that works before this one. Try to keep responsibilities of the throttling package low

bitter jetty
#

What about middleware

#

on every route

#

like I already do to check if IP is internal

static zinc
#

That could work too

bitter jetty
#

mhm

#

I can try tomorrow

#

I will try both options

#

see which works best

bitter jetty
#

alr im here

#

faking work yapping all my time

bitter jetty
#

happydonce it works