Skip to content

rrd108/nuxt-api-shield

Repository files navigation

Nuxt API Shield

npm version npm downloads License Nuxt

This Nuxt module implements a rate limiting middleware to protect your API endpoints from excessive requests.

Features

  • IP-Based Rate limiting and Brute Force Protection
    • Tracks and enforces rate limits for individual IP addresses.
    • Prevents malicious actors or excessive requests from a single source from overwhelming your API.
  • Customizable Rate Limits
    • Configure maximum request count, duration within which the limit applies, and a ban period for exceeding the limit.
    • Add a delay to responses when a user is banned to discourage further abuse.
    • Customize the error message for banned users.
    • Optionally include the Retry-After header in responses when a user is banned.
    • Tailor the rate-limiting behavior to align with your API's specific needs and usage patterns.
  • Event-Driven Handling
    • Intercepts incoming API requests efficiently using Nuxt's event system.
    • Ensures seamless integration with your Nuxt application's request lifecycle.
  • Flexible Storage
    • Utilizes Nuxt's unstorage abstraction for versatile storage options.
    • Store rate-limiting data in various storage providers (filesystem, memory, databases, etc.) based on your project's requirements.
  • Configurable with Runtime Config
    • Easily adjust rate-limiting parameters without code changes.
    • Adapt to dynamic needs and maintain control over rate-limiting behavior through Nuxt's runtime configuration.
  • Clear Error Handling
    • Returns a standardized 429 "Too Many Requests" error response when rate limits are exceeded.
    • Facilitates proper error handling in client-side applications for a smooth user experience.

Quick Setup

1. Add nuxt-api-shield dependency to your project

# Using pnpm
pnpm add nuxt-api-shield

# Using yarn
yarn add nuxt-api-shield

# Using npm
npm install nuxt-api-shield

2. Add nuxt-api-shield to the modules section of nuxt.config.ts

You should add only the values you want to use differently from the default values.

export default defineNuxtConfig({
  modules: ["nuxt-api-shield"],
  nuxtApiShield: {
    /*limit: {
      max: 12,        // maximum requests per duration time, default is 12/duration
      duration: 108,   // duration time in seconds, default is 108 seconds
      ban: 3600,      // ban time in seconds, default is 3600 seconds = 1 hour
    },
    delayOnBan: true  // delay every response with +1sec when the user is banned, default is true
    errorMessage: "Too Many Requests",  // error message when the user is banned, default is "Too Many Requests"
    retryAfterHeader: false, // when the user is banned add the Retry-After header to the response, default is false
    log: {
      path: "logs", // path to the log file, every day a new log file will be created, use "" to disable logging
      attempts: 100,    // if an IP reach 100 requests, all the requests will be logged, can be used for further analysis or blocking for example with fail2ban, use 0 to disable logging
    },
    routes: [], // specify routes to apply rate limiting to, default is an empty array meaning all routes are protected.
    // Example:
    // routes: ["/api/v2/", "/api/v3/"], // /api/v1 will not be protected, /api/v2/ and /api/v3/ will be protected */
  },
});

3. Add nitro/storage to nuxt.config.ts

You can use any storage you want, but you have to use shield as the name of the storage.

{
  "nitro": {
    "storage": {
      "shield": {
        // storage name, you **must** use "shield" as the name
        "driver": "memory"
      }
    }
  }
}

If you use for example redis, you can use the following configuration, define the host and port.

{
  "nitro": {
    "storage": {
      "shield": {
        "driver": "redis",
        "host": "localhost",
        "port": 6379,
      }
    }
  }
}

4. Add shield:clean to nuxt.config.ts

{
  "nitro": {
    "experimental": {
      "tasks": true
    },
    "scheduledTasks": {
      "*/15 * * * *": ["shield:clean"] // clean the shield storage every 15 minutes
    }
  }
}

5. Create your clean task

In server/tasks/shield/clean.ts you should have something like this.

import type { RateLimit } from "#imports";

export default defineTask({
  meta: {
    description: "Clean expired bans",
  },
  async run() {
    const shieldStorage = useStorage("shield");

    const keys = await shieldStorage.getKeys();
    keys.forEach(async (key) => {
      const rateLimit = (await shieldStorage.getItem(key)) as RateLimit;
      if (isBanExpired(rateLimit)) {
        await shieldStorage.removeItem(key);
      }
    });
    return { result: keys };
  },
});

Development

# Install dependencies
yarn

# Generate type stubs
yarn dev:prepare

# Develop with the playground
yarn dev

# Build the playground
yarn dev:build

# Run ESLint
yarn lint

# Run Vitest
yarn test
yarn test:watch

# Release new version
yarn release:patch
yarn release:minor