Search code examples
node.jsrate-limitingfastify

fastify rate limit based on condition


in my fastify project i would like to apply rate limit on some routes conditionally based on the validity of an api key. if the validate api function returns false, then limit. if returns true then apply no limits or higher limit.

I tried to use keygenerator as explained in the documentation, but since the key must be unique. the limit is grouping clients into two big groups. if client 1 is rate limited then other clients are.

import Fastify from 'fastify';
import fastifyRateLimit from '@fastify/rate-limit';
import dotenv from 'dotenv';
import { redisConnector, validateToken } from './redis/redisConnector.js';
import { psqlConnector } from './database/psqlConnector.js';
import { rpcPost, getRandomNode } from './helpers/rpc.js';

dotenv.config();

const fastify = Fastify({
  logger: true
});

redisConnector(fastify);
psqlConnector(fastify);
await fastify.register(fastifyRateLimit, {
  async keyGenerator (request) { return await validateToken(fastify, request.headers['x-api-key']) },
  max: async (request, key) => { return key === 'true' ? 1000 : 1 },
  timeWindow: '1 minute',
  global: true,
});


fastify.get('/', async (request, reply) => {
  return { hello: 'world' };
});


fastify.post('/test', async (request, reply) => {
  let isValidApiKey = 'false';
  if (request.headers["x-api-key"]) {
    isValidApiKey = await validateToken(fastify, request.headers["x-api-key"]);
    console.log(isValidApiKey);
  }
  
  return { foo: 'bar' };;
});


fastify.listen({ port: process.env.FASTIFY_PORT || 3000 }, (err, address) => {
  if (err) throw err;
});

any other idea? maybe setting global: false and using a preHandler? Any example is really much appreciated


Solution

  • The keyGenerator function returns true or false so all the routes are assigned to one of them.

    You should return something that is unique for the user. Morevore I would move out the validateToken logic from the rate limit one:

    // add first an hook to validate the input:
    fastify.addHook('onRequest', async (request) => {
      return await validateToken(fastify, request.headers['x-api-key'])
    })
    
    // then register the plugin:
    await fastify.register(fastifyRateLimit, {
      async keyGenerator (request) { 
        // return something that is unique for the user, like a piece of the api key or an hash
        return request.headers['x-api-key'].slice(-5)
      },
      max: async (request, key) => { return key === 'true' ? 1000 : 1 },
      timeWindow: '1 minute',
      global: true,
    });