leonitousconforti/tinyburg

View on GitHub
apps/authproxy/src/auth/rate-limit.ts

Summary

Maintainability
A
25 mins
Test Coverage
import type { Redis } from "ioredis";
import type { FastifyRequest } from "fastify";
import type { RateLimitPluginOptions } from "@fastify/rate-limit";

export const buildRateLimitConfig = (redis: Redis): RateLimitPluginOptions => ({
    // Apply the rate limit setting to all routes within the encapsulation scope
    global: true,

    // The maximum number of 429 responses to return to a single client before returning 403
    ban: 3,

    // The duration of the time window
    timeWindow: "2 minute",

    // Renew user limitation when user sends a request to the server when still limited
    continueExceeding: true,

    // Do not skip errors generated by the storage (e.g. redis not reachable)
    skipOnError: false,

    // To achieve the maximum speed, this plugin requires the use of ioredis
    redis,

    // Show all the response headers when rate limit is not reached
    addHeadersOnExceeding: {
        "x-ratelimit-limit": true,
        "x-ratelimit-remaining": true,
        "x-ratelimit-reset": true,
    },

    // Show all the response headers when rate limit is reached
    addHeaders: {
        "x-ratelimit-limit": true,
        "x-ratelimit-remaining": true,
        "x-ratelimit-reset": true,
        "retry-after": true,
    },

    // Key Generator should try to use the api key id otherwise fall back to IP
    keyGenerator: (request) => request.apiKey?.id || request.ip,

    // The maximum number of requests a single client can perform inside a time window.
    async max(request: FastifyRequest) {
        const rl = request.apiKey?.rateLimitPerWindow || 5;
        request.log.debug(`This request is rate-limited to ${rl} requests per 2 minute window`);

        // Update the last used date on the api key
        if (request.apiKey) {
            request.apiKey.lastUsed = new Date();
            request.log.debug(`Set api key last used date to ${request.apiKey.lastUsed.toDateString()}`);
            await request.apiKey.save();
        }

        return rl;
    },

    // Write some nice messages when rate limit is hit
    errorResponseBuilder: (request, context) => {
        if (context.ban) {
            return {
                statusCode: 403,
                error: "Forbidden",
                message: `You can not access this service as you have sent too many requests that exceed your rate limit. Your IP: ${request.ip} and Limit: ${context.max}`,
            };
        }

        return {
            statusCode: 429,
            error: "Too Many Requests",
            message: `You hit the rate limit, please slow down! You can retry in ${context.after}`,
        };
    },
});

export default buildRateLimitConfig;