From d69bcbd29df6aef3d3f56fbeda6bf913108e9cd3 Mon Sep 17 00:00:00 2001 From: Adil Date: Thu, 19 Dec 2024 17:33:39 +0100 Subject: [PATCH] chore: Update rate limit middleware and documentation - Bump package version from 2.0.0 to 2.0.2. - Enhance README.md to clarify the plugin's functionality as a middleware utility. - Introduce `configureDefaults` function for setting global rate limit options. - Replace deprecated `getExtendedPluginOptions` with `getDefaultOptions` for improved configuration management. - Add `includeHeaders` option to the default configuration. - Update `.gitignore` to include new TypeScript build info files. --- .gitignore | 3 +- README.md | 75 ++++++++++++++--------- package.json | 2 +- src/api/middlewares/default-rate-limit.ts | 8 +-- src/index.ts | 1 + src/utils/configure-defaults.ts | 6 ++ src/utils/get-default-options.ts | 12 ++++ src/utils/get-extended-plugin-options.ts | 37 ----------- src/utils/global-configuration.ts | 34 ++++++++++ 9 files changed, 106 insertions(+), 72 deletions(-) create mode 100644 src/utils/configure-defaults.ts create mode 100644 src/utils/get-default-options.ts delete mode 100644 src/utils/get-extended-plugin-options.ts create mode 100644 src/utils/global-configuration.ts diff --git a/.gitignore b/.gitignore index b0af995..a7bed6f 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,5 @@ bun.lockb /subscribers /__mocks__ -tsconfig.tsbuildinfo \ No newline at end of file +tsconfig.tsbuildinfo +tsconfig.build.tsbuildinfo \ No newline at end of file diff --git a/README.md b/README.md index 95cd13d..4732e93 100644 --- a/README.md +++ b/README.md @@ -24,14 +24,14 @@

- A simple rate-limitting plugin for Medusa. + A simple rate-limitting utility for Medusa.

Purpose

-

This plugin was mainly built to protect your MedusaJS application from abuse. This plugin allows you to easily manage request limits without stepping outside the familiar Medusa environment.

+

This utility middleware was mainly built to protect your MedusaJS application from abuse. It allows you to easily manage request limits without stepping outside the familiar Medusa environment.

Why Rate Limiting Matters

@@ -53,7 +53,7 @@ npm install @perseidesjs/medusa-plugin-rate-limit Usage

-This plugin uses the CacheModule available (InMemory, Redis, etc.) under the hood and exposes a simple middleware to limit the number of requests per IP address. +This middleware uses the CacheModule available (InMemory, Redis, etc.) under the hood and exposes a simple middleware to limit the number of requests per IP address.

@@ -107,11 +107,11 @@ export default defineMiddlewares({

Granular control over rate limiting

- The choice of having options directly inside the middleware instead of globally inside the plugin options was made to provide greater flexibility. This approach allows users to be more or less restrictive on certain specific routes. By specifying options directly within the middleware, you can tailor the rate limiting mechanism to suit the needs of individual routes, rather than applying a one-size-fits-all configuration globally. This ensures that you can have fine-grained control over the rate limiting behavior, making it possible to adjust the limits based on the specific requirements of each route. + The choice of having options directly inside the middleware instead of globally inside the global options (as on version 1.x of the project) was made to provide greater flexibility. This approach allows users to be more or less restrictive on certain specific routes. By specifying options directly within the middleware, you can tailor the rate limiting mechanism to suit the needs of individual routes, rather than applying a one-size-fits-all configuration globally. This ensures that you can have fine-grained control over the rate limiting behavior, making it possible to adjust the limits based on the specific requirements of each route.

- Additionally, you can still use the plugin options to update the default global values, such as the limit and window. This allows you to set your own default values that will be applied across many routes, while still having the flexibility to specify more granular settings for specific routes. By configuring the plugin options, you can establish a baseline rate limiting policy that suits the majority of your application, and then override these defaults as needed for particular routes. + Additionally, you can use a exported function called `configureDefaults` to update the default global values, such as the `limit`, `window` and `includeHeaders`. This allows you to set your own default values that will be applied across many routes, while still having the flexibility to specify more granular settings for specific routes. By configuring the middleware options, you can establish a baseline rate limiting policy that suits the majority of your application, and then override these defaults as needed for particular routes.

Default configuration

@@ -138,40 +138,57 @@ export default defineMiddlewares({ 60 The time window in seconds + + includeHeaders + Boolean + false + Whether to include the headers (X-RateLimit-Limit, X-RateLimit-Remaining) in the response + -

Plugin options

+

Overriding default options

```ts -// medusa-config.js -const { loadEnv, defineConfig } = require('@medusajs/framework/utils') - -loadEnv(process.env.NODE_ENV, process.cwd()) - -module.exports = defineConfig({ - projectConfig: { - databaseUrl: process.env.DATABASE_URL, - http: { - storeCors: process.env.STORE_CORS, - adminCors: process.env.ADMIN_CORS, - authCors: process.env.AUTH_CORS, - jwtSecret: process.env.JWT_SECRET || "supersecret", - cookieSecret: process.env.COOKIE_SECRET || "supersecret", - }, - }, - plugins: [ +// + +import { + defineMiddlewares +} from "@medusajs/framework/http" +import { defaultRateLimit, configureDefaults } from '@perseidesjs/medusa-plugin-rate-limit' + +// This will override the global default options for all routes +// Here, we set the limit to 1 request per 30 seconds +configureDefaults({ + limit: 1, + window: 30, +}) + +export default defineMiddlewares({ + routes: [ { - resolve: "@perseidesjs/medusa-plugin-rate-limit", - options: { - limit: 50, - window: 60, - }, + matcher: "/store/custom*", + method: "POST", + middlewares: [ + // Uses the global default options + defaultRateLimit() + ], + }, + { + matcher: "/store/custom*", + method: "POST", + middlewares: [ + // If the options are provided, they will ignore the global default options and use the provided ones + defaultRateLimit({ + limit: 10, + window: 60, + }) + ], }, ], }) ```

License

-

This project is licensed under the MIT License - see the LICENSE file for details

\ No newline at end of file +

This project is licensed under the MIT License - see the LICENSE file for details

diff --git a/package.json b/package.json index 6d1fa9e..159ef7b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@perseidesjs/medusa-plugin-rate-limit", - "version": "2.0.0", + "version": "2.0.2", "description": "A simple rate-limit middleware for Medusa", "author": "adevinwild", "license": "MIT", diff --git a/src/api/middlewares/default-rate-limit.ts b/src/api/middlewares/default-rate-limit.ts index d91d818..a7e8306 100644 --- a/src/api/middlewares/default-rate-limit.ts +++ b/src/api/middlewares/default-rate-limit.ts @@ -7,18 +7,18 @@ import { type ICacheService } from '@medusajs/framework/types' import { Modules } from '@medusajs/framework/utils' import { type PluginOptions } from '../../constants' -import { getExtendedPluginOptions } from '../../utils/get-extended-plugin-options' +import { getDefaultOptions } from '../../utils/get-default-options' import { getIp } from '../../utils/get-ip' import { setRateLimitHeaders } from '../../utils/set-rate-limit-headers' -export function defaultRateLimit(options: Partial | undefined) { +export function defaultRateLimit(options: Partial = {}) { return async function ( req: MedusaRequest, res: MedusaResponse, next: MedusaNextFunction, ) { const { limit, window, includeHeaders } = { - ...getExtendedPluginOptions(req.scope), + ...getDefaultOptions(), ...options, } @@ -44,4 +44,4 @@ export function defaultRateLimit(options: Partial | undefined) { next() } -} +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 5411cb9..c98014b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,2 @@ export { defaultRateLimit } from './api/middlewares/default-rate-limit' +export { configureDefaults } from './utils/configure-defaults' \ No newline at end of file diff --git a/src/utils/configure-defaults.ts b/src/utils/configure-defaults.ts new file mode 100644 index 0000000..108bf56 --- /dev/null +++ b/src/utils/configure-defaults.ts @@ -0,0 +1,6 @@ +import { type PluginOptions } from '../constants' +import { configManager } from './global-configuration' + +export function configureDefaults(options: Partial): void { + configManager.setConfig(options) +} \ No newline at end of file diff --git a/src/utils/get-default-options.ts b/src/utils/get-default-options.ts new file mode 100644 index 0000000..d192018 --- /dev/null +++ b/src/utils/get-default-options.ts @@ -0,0 +1,12 @@ +import { type PluginOptions } from '../constants' +import { configManager } from './global-configuration' + +export function getDefaultOptions(): PluginOptions { + const globalConfig = configManager.getConfig() + + return { + limit: globalConfig.limit, + window: globalConfig.window, + includeHeaders: globalConfig.includeHeaders, + } +} diff --git a/src/utils/get-extended-plugin-options.ts b/src/utils/get-extended-plugin-options.ts deleted file mode 100644 index 9d7f3eb..0000000 --- a/src/utils/get-extended-plugin-options.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { type ConfigModule, type MedusaRequest } from '@medusajs/framework' -import { DEFAULT_OPTIONS, type PluginOptions } from '../constants' - -const PLUGIN_RESOLUTION_PATH = '@perseidesjs/medusa-plugin-rate-limit' - -export function getExtendedPluginOptions( - scope: MedusaRequest['scope'], -): PluginOptions { - const configModule = scope.resolve('configModule') - const plugins = configModule.plugins - - if (!plugins || plugins.length === 0) { - return DEFAULT_OPTIONS - } - - const hasResolveAndOptions = ( - plugin: (typeof plugins)[number], - ): plugin is { resolve: string; options: Record } => { - return ( - typeof plugin === 'object' && - plugin !== null && - 'resolve' in plugin && - 'options' in plugin - ) - } - const foundPlugin = plugins - .filter(hasResolveAndOptions) - .find((plugin) => plugin.resolve === PLUGIN_RESOLUTION_PATH) - - const options = foundPlugin?.options as Partial - - return { - limit: options.limit ?? DEFAULT_OPTIONS.limit, - window: options.window ?? DEFAULT_OPTIONS.window, - includeHeaders: options.includeHeaders ?? DEFAULT_OPTIONS.includeHeaders, - } -} diff --git a/src/utils/global-configuration.ts b/src/utils/global-configuration.ts new file mode 100644 index 0000000..1cc38a6 --- /dev/null +++ b/src/utils/global-configuration.ts @@ -0,0 +1,34 @@ +import { type PluginOptions } from '../constants' + +class ConfigurationManager { + private static instance: ConfigurationManager + private config: PluginOptions + + private constructor() { + this.config = { + limit: 5, + window: 60, + includeHeaders: true + } + } + + public static getInstance(): ConfigurationManager { + if (!ConfigurationManager.instance) { + ConfigurationManager.instance = new ConfigurationManager() + } + return ConfigurationManager.instance + } + + public setConfig(options: Partial): void { + this.config = { + ...this.config, + ...options + } + } + + public getConfig(): PluginOptions { + return { ...this.config } + } +} + +export const configManager = ConfigurationManager.getInstance() \ No newline at end of file