Skip to content

Commit

Permalink
Merge pull request #7 from perseidesjs/release-2-0-0
Browse files Browse the repository at this point in the history
chore: Update rate limit middleware and documentation
  • Loading branch information
adevinwild authored Dec 19, 2024
2 parents beff6e3 + d69bcbd commit 434a182
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 72 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ bun.lockb
/subscribers
/__mocks__

tsconfig.tsbuildinfo
tsconfig.tsbuildinfo
tsconfig.build.tsbuildinfo
75 changes: 46 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@
</h4>

<p align="center">
A simple rate-limitting plugin for Medusa.
A simple rate-limitting utility for Medusa.
</p>

<h2>
Purpose
</h2>

<p>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.</p>
<p>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.</p>

<h3>Why Rate Limiting Matters</h3>

Expand All @@ -53,7 +53,7 @@ npm install @perseidesjs/medusa-plugin-rate-limit
Usage
</h2>
<p>
This plugin uses the <a href="https://docs.medusajs.com/v2/resources/architectural-modules/cache#main">CacheModule</a> available (<i>InMemory, Redis, etc.</i>) under the hood and exposes a simple middleware to limit the number of requests per IP address.
This middleware uses the <a href="https://docs.medusajs.com/resources/architectural-modules/cache#main">CacheModule</a> available (<i>InMemory, Redis, etc.</i>) under the hood and exposes a simple middleware to limit the number of requests per IP address.
</p>

<h2>
Expand Down Expand Up @@ -107,11 +107,11 @@ export default defineMiddlewares({
<h3>Granular control over rate limiting</h3>

<p>
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.
</p>

<p>
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.
</p>

<h3> Default configuration </h3>
Expand All @@ -138,40 +138,57 @@ export default defineMiddlewares({
<td><code>60</code></td>
<td>The time window in seconds</td>
</tr>
<tr>
<td>includeHeaders</td>
<td><code>Boolean</code></td>
<td><code>false</code></td>
<td>Whether to include the headers (<code>X-RateLimit-Limit</code>, <code>X-RateLimit-Remaining</code>) in the response</td>
</tr>
</tbody>
</table>


<h3> Plugin options </h3>
<h3>Overriding default options</h3>

```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,
})
],
},
],
})
```

<h2>License</h2>
<p> This project is licensed under the MIT License - see the <a href="./LICENSE.md">LICENSE</a> file for details</p>
<p> This project is licensed under the MIT License - see the <a href="./LICENSE.md">LICENSE</a> file for details</p>
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
8 changes: 4 additions & 4 deletions src/api/middlewares/default-rate-limit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<PluginOptions> | undefined) {
export function defaultRateLimit(options: Partial<PluginOptions> = {}) {
return async function (
req: MedusaRequest,
res: MedusaResponse,
next: MedusaNextFunction,
) {
const { limit, window, includeHeaders } = {
...getExtendedPluginOptions(req.scope),
...getDefaultOptions(),
...options,
}

Expand All @@ -44,4 +44,4 @@ export function defaultRateLimit(options: Partial<PluginOptions> | undefined) {

next()
}
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { defaultRateLimit } from './api/middlewares/default-rate-limit'
export { configureDefaults } from './utils/configure-defaults'
6 changes: 6 additions & 0 deletions src/utils/configure-defaults.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { type PluginOptions } from '../constants'
import { configManager } from './global-configuration'

export function configureDefaults(options: Partial<PluginOptions>): void {
configManager.setConfig(options)
}
12 changes: 12 additions & 0 deletions src/utils/get-default-options.ts
Original file line number Diff line number Diff line change
@@ -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,
}
}
37 changes: 0 additions & 37 deletions src/utils/get-extended-plugin-options.ts

This file was deleted.

34 changes: 34 additions & 0 deletions src/utils/global-configuration.ts
Original file line number Diff line number Diff line change
@@ -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<PluginOptions>): void {
this.config = {
...this.config,
...options
}
}

public getConfig(): PluginOptions {
return { ...this.config }
}
}

export const configManager = ConfigurationManager.getInstance()

0 comments on commit 434a182

Please sign in to comment.