Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic rate limiter whitelist (specific IPs only) #573

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion docs/content/3.middleware/1.rate-limiter.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type RateLimiter = {
tokensPerInterval: number;
interval: number;
headers: boolean;
whiteList: string[];
throwError: boolean;
driver: {
name: string;
Expand All @@ -82,11 +83,17 @@ The time value in miliseconds after which the rate limiting will be reset. For e

When set to `true` it will set the response headers: `X-Ratelimit-Remaining`, `X-Ratelimit-Reset`, `X-Ratelimit-Limit` with appriopriate values.

### `whiteList`

- Default: `undefined`

When set to `['127.0.0.1', '192.168.0.1']` it will skip rate limiting for these specific IPs.

### `throwError`

- Default: `true`

Whether to throw Nuxt Error with appriopriate error code and message. If set to false, it will just return the object with the error that you can handle.
Whether to throw Nuxt Error with appropriate error code and message. If set to false, it will just return the object with the error that you can handle.

### `driver`

Expand Down
11 changes: 10 additions & 1 deletion playground/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,16 @@ export default defineNuxtConfig({
referrerPolicy: false
}
}
}
},
'/rateLimit': {
security: {
rateLimiter: {
tokensPerInterval:1,
interval: 1000,
whiteList: ['127.0.0.1'],
},
},
},
},

// Global configuration
Expand Down
5 changes: 5 additions & 0 deletions playground/pages/rateLimit.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<div>
RateLimit tests Route
</div>
</template>
1 change: 1 addition & 0 deletions src/defaultConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export const defaultSecurityConfig = (serverlUrl: string, strict: boolean) => {
driver: {
name: 'lruCache'
},
whiteList: undefined,
...defaultThrowErrorValue
},
xssValidator: {
Expand Down
3 changes: 3 additions & 0 deletions src/runtime/server/middleware/rateLimiter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export default defineEventHandler(async(event) => {
defaultRateLimiter
)
const ip = getIP(event)
if(rateLimiter.whiteList && rateLimiter.whiteList.includes(ip)){
return
}
const url = ip + route

let storageItem = await storage.getItem(url) as StorageItem
Expand Down
1 change: 1 addition & 0 deletions src/types/middlewares.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export type RateLimiter = {
options?: BuiltinDriverOptions[driverName] }
}[BuiltinDriverName];
headers?: boolean;
whiteList?: string[];
throwError?: boolean;
};

Expand Down
64 changes: 63 additions & 1 deletion test/fixtures/rateLimiter/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,68 @@ export default defineNuxtConfig({
tokensPerInterval: 10,
}
}
}
},
'/whitelistBase': {
security: {
rateLimiter: {
tokensPerInterval: 1,
interval: 300000,
whiteList: [
'127.0.0.1',
'192.168.0.1',
'172.16.0.1',
'10.0.0.1',
],
}
}
},
'/whitelistEmpty': {
security: {
rateLimiter: {
tokensPerInterval: 1,
interval: 300000,
whiteList: [],
}
}
},
'/whitelistNotListed': {
security: {
rateLimiter: {
tokensPerInterval: 1,
interval: 300000,
whiteList: [
'10.0.0.1',
'10.0.1.1',
'10.0.2.1',
'10.0.3.1',
'10.0.4.1',
'10.0.5.1',
'10.0.6.1',
'10.0.7.1',
'10.0.8.1',
'10.0.9.1',
'10.1.0.1',
'10.2.0.1',
'10.3.0.1',
'10.4.0.1',
'10.5.0.1',
'10.6.0.1',
'10.7.0.1',
'10.8.0.1',
'10.9.0.1',
'192.168.0.1',
'192.168.1.1',
'192.168.2.1',
'192.168.3.1',
'192.168.4.1',
'192.168.5.1',
'192.168.6.1',
'192.168.7.1',
'192.168.8.1',
'192.168.9.1',
],
}
}
},
}
})
3 changes: 3 additions & 0 deletions test/fixtures/rateLimiter/pages/whitelistBase.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<div>whitelist base test</div>
</template>
3 changes: 3 additions & 0 deletions test/fixtures/rateLimiter/pages/whitelistEmpty.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<div>whitelist empty test</div>
</template>
3 changes: 3 additions & 0 deletions test/fixtures/rateLimiter/pages/whitelistNotListed.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<div>whitelist not listed test</div>
</template>
39 changes: 39 additions & 0 deletions test/rateLimiter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,43 @@ describe('[nuxt-security] Rate Limiter', async () => {
expect(res6.status).toBe(200)
expect(res6.statusText).toBe('OK')
})

it ('should return 200 OK after multiple requests for a route with localhost ip whitelisted', async () => {
const res1 = await fetch('/whitelistBase')
await fetch('/whitelistBase')
await fetch('/whitelistBase')
await fetch('/whitelistBase')
const res5 = await fetch('/whitelistBase')

expect(res1).toBeDefined()
expect(res1).toBeTruthy()
expect(res5.status).toBe(200)
expect(res5.statusText).toBe('OK')
})

it ('should return 429 when limit reached with an empty whitelist array', async () => {
const res1 = await fetch('/whitelistEmpty')
await fetch('/whitelistEmpty')
await fetch('/whitelistEmpty')
await fetch('/whitelistEmpty')
const res5 = await fetch('/whitelistEmpty')

expect(res1).toBeDefined()
expect(res1).toBeTruthy()
expect(res5.status).toBe(429)
expect(res5.statusText).toBe('Too Many Requests')
})

it ('should return 429 when limit reached as localhost ip is not whitelisted', async () => {
const res1 = await fetch('/whitelistNotListed')
await fetch('/whitelistNotListed')
await fetch('/whitelistNotListed')
await fetch('/whitelistNotListed')
const res5 = await fetch('/whitelistNotListed')

expect(res1).toBeDefined()
expect(res1).toBeTruthy()
expect(res5.status).toBe(429)
expect(res5.statusText).toBe('Too Many Requests')
})
})