Skip to content

Commit 99019bb

Browse files
authored
Merge pull request #607 from Baroshem/chore/2.2.0
Chore/2.2.0
2 parents 977ae70 + d300486 commit 99019bb

File tree

14 files changed

+145
-6
lines changed

14 files changed

+145
-6
lines changed

docs/content/3.middleware/1.rate-limiter.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ type RateLimiter = {
5656
tokensPerInterval: number;
5757
interval: number;
5858
headers: boolean;
59+
whiteList: string[];
5960
throwError: boolean;
6061
driver: {
6162
name: string;
@@ -82,11 +83,17 @@ The time value in miliseconds after which the rate limiting will be reset. For e
8283

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

86+
### `whiteList`
87+
88+
- Default: `undefined`
89+
90+
When set to `['127.0.0.1', '192.168.0.1']` it will skip rate limiting for these specific IPs.
91+
8592
### `throwError`
8693

8794
- Default: `true`
8895

89-
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.
96+
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.
9097

9198
### `driver`
9299

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nuxt-security",
3-
"version": "2.1.4",
3+
"version": "2.2.0",
44
"license": "MIT",
55
"type": "module",
66
"engines": {

playground/nuxt.config.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,16 @@ export default defineNuxtConfig({
4242
referrerPolicy: false
4343
}
4444
}
45-
}
45+
},
46+
'/rateLimit': {
47+
security: {
48+
rateLimiter: {
49+
tokensPerInterval:1,
50+
interval: 1000,
51+
whiteList: ['127.0.0.1'],
52+
},
53+
},
54+
},
4655
},
4756

4857
// Global configuration

playground/pages/rateLimit.vue

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<template>
2+
<div>
3+
RateLimit tests Route
4+
</div>
5+
</template>

src/defaultConfig.ts

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export const defaultSecurityConfig = (serverlUrl: string, strict: boolean) => {
5555
driver: {
5656
name: 'lruCache'
5757
},
58+
whiteList: undefined,
5859
...defaultThrowErrorValue
5960
},
6061
xssValidator: {

src/runtime/nitro/plugins/40-cspSsrNonce.ts

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { resolveSecurityRules } from '../context'
33
import { generateRandomNonce } from '../../../utils/crypto'
44

55
const LINK_RE = /<link([^>]*?>)/gi
6+
const NONCE_RE = /nonce="[^"]+"/i
67
const SCRIPT_RE = /<script([^>]*?>)/gi
78
const STYLE_RE = /<style([^>]*?>)/gi
89

@@ -58,6 +59,9 @@ export default defineNitroPlugin((nitroApp) => {
5859
}
5960
// Add nonce to all link tags
6061
element = element.replace(LINK_RE, (match, rest) => {
62+
if (NONCE_RE.test(rest)) {
63+
return match.replace(NONCE_RE, `nonce="${nonce}"`);
64+
}
6165
return `<link nonce="${nonce}"` + rest
6266
})
6367
// Add nonce to all script tags

src/runtime/server/middleware/basicAuth.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,9 @@ export type BasicAuth = {
1616
message: string;
1717
}
1818

19-
const securityConfig = useRuntimeConfig().private
20-
2119
export default defineEventHandler((event) => {
2220
const credentials = getCredentials(event.node.req)
21+
const securityConfig = useRuntimeConfig(event).private
2322
const basicAuthConfig = securityConfig.basicAuth
2423

2524
if (!basicAuthConfig) {

src/runtime/server/middleware/rateLimiter.ts

+3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ export default defineEventHandler(async(event) => {
2828
defaultRateLimiter
2929
)
3030
const ip = getIP(event)
31+
if(rateLimiter.whiteList && rateLimiter.whiteList.includes(ip)){
32+
return
33+
}
3134
const url = ip + route
3235

3336
let storageItem = await storage.getItem(url) as StorageItem

src/types/middlewares.ts

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export type RateLimiter = {
1919
options?: BuiltinDriverOptions[driverName] }
2020
}[BuiltinDriverName];
2121
headers?: boolean;
22+
whiteList?: string[];
2223
throwError?: boolean;
2324
};
2425

test/fixtures/rateLimiter/nuxt.config.ts

+63-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,68 @@ export default defineNuxtConfig({
1515
tokensPerInterval: 10,
1616
}
1717
}
18-
}
18+
},
19+
'/whitelistBase': {
20+
security: {
21+
rateLimiter: {
22+
tokensPerInterval: 1,
23+
interval: 300000,
24+
whiteList: [
25+
'127.0.0.1',
26+
'192.168.0.1',
27+
'172.16.0.1',
28+
'10.0.0.1',
29+
],
30+
}
31+
}
32+
},
33+
'/whitelistEmpty': {
34+
security: {
35+
rateLimiter: {
36+
tokensPerInterval: 1,
37+
interval: 300000,
38+
whiteList: [],
39+
}
40+
}
41+
},
42+
'/whitelistNotListed': {
43+
security: {
44+
rateLimiter: {
45+
tokensPerInterval: 1,
46+
interval: 300000,
47+
whiteList: [
48+
'10.0.0.1',
49+
'10.0.1.1',
50+
'10.0.2.1',
51+
'10.0.3.1',
52+
'10.0.4.1',
53+
'10.0.5.1',
54+
'10.0.6.1',
55+
'10.0.7.1',
56+
'10.0.8.1',
57+
'10.0.9.1',
58+
'10.1.0.1',
59+
'10.2.0.1',
60+
'10.3.0.1',
61+
'10.4.0.1',
62+
'10.5.0.1',
63+
'10.6.0.1',
64+
'10.7.0.1',
65+
'10.8.0.1',
66+
'10.9.0.1',
67+
'192.168.0.1',
68+
'192.168.1.1',
69+
'192.168.2.1',
70+
'192.168.3.1',
71+
'192.168.4.1',
72+
'192.168.5.1',
73+
'192.168.6.1',
74+
'192.168.7.1',
75+
'192.168.8.1',
76+
'192.168.9.1',
77+
],
78+
}
79+
}
80+
},
1981
}
2082
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<template>
2+
<div>whitelist base test</div>
3+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<template>
2+
<div>whitelist empty test</div>
3+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<template>
2+
<div>whitelist not listed test</div>
3+
</template>

test/rateLimiter.test.ts

+39
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,43 @@ describe('[nuxt-security] Rate Limiter', async () => {
6363
expect(res6.status).toBe(200)
6464
expect(res6.statusText).toBe('OK')
6565
})
66+
67+
it ('should return 200 OK after multiple requests for a route with localhost ip whitelisted', async () => {
68+
const res1 = await fetch('/whitelistBase')
69+
await fetch('/whitelistBase')
70+
await fetch('/whitelistBase')
71+
await fetch('/whitelistBase')
72+
const res5 = await fetch('/whitelistBase')
73+
74+
expect(res1).toBeDefined()
75+
expect(res1).toBeTruthy()
76+
expect(res5.status).toBe(200)
77+
expect(res5.statusText).toBe('OK')
78+
})
79+
80+
it ('should return 429 when limit reached with an empty whitelist array', async () => {
81+
const res1 = await fetch('/whitelistEmpty')
82+
await fetch('/whitelistEmpty')
83+
await fetch('/whitelistEmpty')
84+
await fetch('/whitelistEmpty')
85+
const res5 = await fetch('/whitelistEmpty')
86+
87+
expect(res1).toBeDefined()
88+
expect(res1).toBeTruthy()
89+
expect(res5.status).toBe(429)
90+
expect(res5.statusText).toBe('Too Many Requests')
91+
})
92+
93+
it ('should return 429 when limit reached as localhost ip is not whitelisted', async () => {
94+
const res1 = await fetch('/whitelistNotListed')
95+
await fetch('/whitelistNotListed')
96+
await fetch('/whitelistNotListed')
97+
await fetch('/whitelistNotListed')
98+
const res5 = await fetch('/whitelistNotListed')
99+
100+
expect(res1).toBeDefined()
101+
expect(res1).toBeTruthy()
102+
expect(res5.status).toBe(429)
103+
expect(res5.statusText).toBe('Too Many Requests')
104+
})
66105
})

0 commit comments

Comments
 (0)