@lidofinance/next-ip-rate-limit 0.59.0 → 0.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +23 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +23 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
var $2Dv6i$net = require("net");
|
|
2
|
+
|
|
1
3
|
function $parcel$exportWildcard(dest, source) {
|
|
2
4
|
Object.keys(source).forEach(function(key) {
|
|
3
5
|
if (key === 'default' || key === '__esModule' || dest.hasOwnProperty(key)) {
|
|
@@ -53,6 +55,7 @@ class $9e3ba9e2f0393d79$export$230ecc6ad0ef6558 {
|
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
|
|
58
|
+
|
|
56
59
|
var $54a02752d7f6015e$exports = {};
|
|
57
60
|
|
|
58
61
|
$parcel$export($54a02752d7f6015e$exports, "RATE_LIMIT_HEADERS", () => $54a02752d7f6015e$export$a063967512de3ebf);
|
|
@@ -65,11 +68,21 @@ const $54a02752d7f6015e$export$a063967512de3ebf = {
|
|
|
65
68
|
const $54a02752d7f6015e$export$3f27bc10068ef877 = "API rate limit exceeded for";
|
|
66
69
|
|
|
67
70
|
|
|
68
|
-
const $c63185ef25ccfd28$export$
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
const $c63185ef25ccfd28$export$73f7dfe1bf8dc8ad = (ip)=>ip.trim().replace(/^::ffff:/i, "");
|
|
72
|
+
// Get first IP address from a header value or return undefined if invalid
|
|
73
|
+
const $c63185ef25ccfd28$var$firstHeaderValue = (header)=>{
|
|
74
|
+
const raw = Array.isArray(header) ? header[0] : header;
|
|
75
|
+
const value = raw ? $c63185ef25ccfd28$export$73f7dfe1bf8dc8ad(raw.split(",")[0]) : undefined;
|
|
76
|
+
return value && (0, $2Dv6i$net.isIP)(value) ? value : undefined;
|
|
77
|
+
};
|
|
78
|
+
const $c63185ef25ccfd28$export$67218453e01a0be8 = (req, customIpHeader)=>{
|
|
79
|
+
// For custom header setup
|
|
80
|
+
const customIpValue = customIpHeader ? $c63185ef25ccfd28$var$firstHeaderValue(req.headers[customIpHeader.toLowerCase()]) : undefined;
|
|
81
|
+
// Default CF header
|
|
82
|
+
const cfConnectionIp = $c63185ef25ccfd28$var$firstHeaderValue(req.headers["cf-connecting-ip"]);
|
|
83
|
+
// Fallback to remote address
|
|
84
|
+
const remoteIp = $c63185ef25ccfd28$export$73f7dfe1bf8dc8ad(req.socket?.remoteAddress || "0.0.0.0");
|
|
85
|
+
return customIpValue || cfConnectionIp || remoteIp;
|
|
73
86
|
};
|
|
74
87
|
const $c63185ef25ccfd28$export$d0a20d44a54350ab = ({ id: id, res: res, req: req, retryAfter: retryAfter })=>{
|
|
75
88
|
res.status(429);
|
|
@@ -83,9 +96,9 @@ let $abe9c276dfd79488$var$rateLimitStorage;
|
|
|
83
96
|
const $abe9c276dfd79488$export$122bebc9e141287c = (data)=>{
|
|
84
97
|
if (!$abe9c276dfd79488$var$rateLimitStorage) $abe9c276dfd79488$var$rateLimitStorage = new (0, $9e3ba9e2f0393d79$export$230ecc6ad0ef6558)();
|
|
85
98
|
const now = Date.now() / 1000;
|
|
86
|
-
const { res: res, req: req, limit: limit, timeFrame: timeFrame } = data;
|
|
99
|
+
const { res: res, req: req, limit: limit, timeFrame: timeFrame, customIpHeader: customIpHeader } = data;
|
|
87
100
|
const headers = (0, $54a02752d7f6015e$export$a063967512de3ebf);
|
|
88
|
-
const id = `ip:${(0, $c63185ef25ccfd28$export$67218453e01a0be8)(req)}`;
|
|
101
|
+
const id = `ip:${(0, $c63185ef25ccfd28$export$67218453e01a0be8)(req, customIpHeader)}`;
|
|
89
102
|
const time = Math.floor(now / timeFrame);
|
|
90
103
|
const key = `${id}:${time}`;
|
|
91
104
|
const callCount = $abe9c276dfd79488$var$rateLimitStorage.inc(key, data.timeFrame);
|
|
@@ -111,12 +124,13 @@ var $bad81bc593a4351d$exports = {};
|
|
|
111
124
|
|
|
112
125
|
$parcel$export($bad81bc593a4351d$exports, "rateLimitWrapper", () => $bad81bc593a4351d$export$1e3e762f475f853);
|
|
113
126
|
|
|
114
|
-
const $bad81bc593a4351d$export$1e3e762f475f853 = ({ rateLimit: rateLimit, rateLimitTimeFrame: rateLimitTimeFrame })=>async (req, res, next)=>{
|
|
127
|
+
const $bad81bc593a4351d$export$1e3e762f475f853 = ({ rateLimit: rateLimit, rateLimitTimeFrame: rateLimitTimeFrame, customIpHeader: customIpHeader })=>async (req, res, next)=>{
|
|
115
128
|
(0, $abe9c276dfd79488$export$122bebc9e141287c)({
|
|
116
129
|
req: req,
|
|
117
130
|
res: res,
|
|
118
131
|
limit: rateLimit,
|
|
119
|
-
timeFrame: rateLimitTimeFrame
|
|
132
|
+
timeFrame: rateLimitTimeFrame,
|
|
133
|
+
customIpHeader: customIpHeader
|
|
120
134
|
});
|
|
121
135
|
await next?.(req, res, next);
|
|
122
136
|
};
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":"
|
|
1
|
+
{"mappings":"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","sources":["packages/next/ip-rate-limit/src/index.ts","packages/next/ip-rate-limit/src/ip-rate-limit.ts","packages/next/ip-rate-limit/src/storage.ts","packages/next/ip-rate-limit/src/utils.ts","packages/next/ip-rate-limit/src/constants.ts","packages/next/ip-rate-limit/src/wrapper.ts","packages/next/ip-rate-limit/src/types.ts"],"sourcesContent":["export * from './ip-rate-limit'\nexport * from './wrapper'\nexport * from './constants'\nexport * from './types'\n","import { RateLimitStorage } from './storage'\nimport { getIP, rateLimitedResponse } from './utils'\nimport { RATE_LIMIT_HEADERS } from './constants'\nimport { RateLimit } from './types'\n\nlet rateLimitStorage: RateLimitStorage\n\nexport const ipRateLimit: RateLimit = (data) => {\n if (!rateLimitStorage) rateLimitStorage = new RateLimitStorage()\n\n const now = Date.now() / 1000\n\n const { res, req, limit, timeFrame, customIpHeader } = data\n const headers = RATE_LIMIT_HEADERS\n const id = `ip:${getIP(req, customIpHeader)}`\n\n const time = Math.floor(now / timeFrame)\n const key = `${id}:${time}`\n\n const callCount = rateLimitStorage.inc(key, data.timeFrame)\n const remaining = limit - (callCount || 0)\n const reset = (time + 1) * timeFrame\n\n res.setHeader(headers.limit, `${limit}`)\n res.setHeader(headers.remaining, `${remaining < 0 ? 0 : remaining}`)\n res.setHeader(headers.reset, `${reset}`)\n\n if (remaining < 0) {\n const retryAfter = Math.ceil(Math.max(reset - now, 0))\n rateLimitedResponse({ id, res, req, retryAfter })\n }\n\n return res\n}\n","import { RateLimitStorageType } from './types'\n\nexport class RateLimitStorage {\n private _storage: RateLimitStorageType\n private timeoutIds: NodeJS.Timeout[] = []\n\n constructor() {\n this._storage = new Map()\n }\n\n set(key: string, value: number, durationSec: number) {\n this._storage.set(key, { value })\n\n if (this._storage.get(key)?.timeoutId) {\n clearTimeout(this._storage.get(key)?.timeoutId)\n }\n\n // code based on the work with setTimeouts from the code from the nestjs throttling service\n // https://github.com/nestjs/throttler/blob/master/src/throttler.service.ts#L25\n const timeoutId = setTimeout(() => {\n clearTimeout(timeoutId)\n\n this.timeoutIds = this.timeoutIds.filter((id) => id != timeoutId)\n this.delete(key)\n }, durationSec * 1000)\n this.timeoutIds.push(timeoutId)\n }\n\n get(key: string) {\n return this._storage.get(key)?.value\n }\n\n inc(key: string, durationSec: number) {\n if (this._storage.has(key)) {\n this.set(key, (this._storage.get(key)?.value || 0) + 1, durationSec)\n } else {\n this.set(key, 1, durationSec)\n }\n\n return this.get(key)\n }\n delete(key: string) {\n if (this._storage.has(key)) {\n this._storage.delete(key)\n }\n }\n}\n","import { isIP } from 'net'\nimport { NextApiRequest } from 'next'\nimport { ERROR_MESSAGE } from './constants'\nimport { RateLimitedResponse } from './types'\n\n// Normalize IP address by trimming whitespace and removing IPv6 prefix if present\nexport const normalizeIP = (ip: string): string => ip.trim().replace(/^::ffff:/i, '')\n\n// Get first IP address from a header value or return undefined if invalid\nconst firstHeaderValue = (header?: string | string[]): string | undefined => {\n const raw = Array.isArray(header) ? header[0] : header\n const value = raw ? normalizeIP(raw.split(',')[0]) : undefined\n\n return value && isIP(value) ? value : undefined\n}\n\n// now we use headers from \"cloudflare\" and rely on their validity.\n// If there are no \"cloudflare\" headers, we use the \"x-forwarded-for\" header,\n// but keep in mind that these headers can be changed by anyone\n// https://developers.cloudflare.com/fundamentals/get-started/reference/http-request-headers/\nexport const getIP = (req: NextApiRequest, customIpHeader?: string): string => {\n // For custom header setup\n const customIpValue = customIpHeader ? firstHeaderValue(req.headers[customIpHeader.toLowerCase()]) : undefined\n\n // Default CF header\n const cfConnectionIp = firstHeaderValue(req.headers['cf-connecting-ip'])\n\n // Fallback to remote address\n const remoteIp = normalizeIP(req.socket?.remoteAddress || '0.0.0.0')\n\n return customIpValue || cfConnectionIp || remoteIp\n}\n\nexport const rateLimitedResponse: RateLimitedResponse = ({ id, res, req, retryAfter }) => {\n res.status(429)\n if (retryAfter) res.setHeader('Retry-After', retryAfter.toString())\n throw new Error(`${ERROR_MESSAGE} ${id} ${req.url || ''}`)\n}\n","export const RATE_LIMIT_HEADERS = {\n limit: `X-RateLimit-Limit`,\n remaining: `X-RateLimit-Remaining`,\n reset: `X-RateLimit-Reset`,\n}\n\nexport const ERROR_MESSAGE = 'API rate limit exceeded for'\n","import { RequestWrapper } from '@lidofinance/next-api-wrapper'\n\nimport { ipRateLimit } from './ip-rate-limit'\nimport { RateLimitWrapperArgs } from './types'\n\nexport const rateLimitWrapper =\n ({ rateLimit, rateLimitTimeFrame, customIpHeader }: RateLimitWrapperArgs): RequestWrapper =>\n async (req, res, next) => {\n ipRateLimit({\n req,\n res,\n limit: rateLimit,\n timeFrame: rateLimitTimeFrame,\n customIpHeader,\n })\n\n await next?.(req, res, next)\n }\n","import { NextApiResponse, NextApiRequest } from 'next'\n\nexport type RateLimitStorageType = Map<string, { value: number; timeoutId?: NodeJS.Timeout }>\n\nexport type RateLimitedResponse = (data: {\n res: NextApiResponse\n id: string\n req: NextApiRequest\n retryAfter?: number\n}) => void\n\nexport type RateLimit = (data: {\n req: NextApiRequest\n res: NextApiResponse\n limit: number\n timeFrame: number\n customIpHeader?: string\n}) => NextApiResponse\n\nexport type RateLimitWrapperArgs = {\n rateLimit: number\n rateLimitTimeFrame: number\n customIpHeader?: string\n}\n"],"names":[],"version":3,"file":"index.cjs.map","sourceRoot":"../../../../"}
|
package/dist/index.d.ts
CHANGED
|
@@ -15,10 +15,12 @@ export type RateLimit = (data: {
|
|
|
15
15
|
res: NextApiResponse;
|
|
16
16
|
limit: number;
|
|
17
17
|
timeFrame: number;
|
|
18
|
+
customIpHeader?: string;
|
|
18
19
|
}) => NextApiResponse;
|
|
19
20
|
export type RateLimitWrapperArgs = {
|
|
20
21
|
rateLimit: number;
|
|
21
22
|
rateLimitTimeFrame: number;
|
|
23
|
+
customIpHeader?: string;
|
|
22
24
|
};
|
|
23
25
|
export const RATE_LIMIT_HEADERS: {
|
|
24
26
|
limit: string;
|
|
@@ -27,6 +29,6 @@ export const RATE_LIMIT_HEADERS: {
|
|
|
27
29
|
};
|
|
28
30
|
export const ERROR_MESSAGE = "API rate limit exceeded for";
|
|
29
31
|
export const ipRateLimit: RateLimit;
|
|
30
|
-
export const rateLimitWrapper: ({ rateLimit, rateLimitTimeFrame }: RateLimitWrapperArgs) => RequestWrapper;
|
|
32
|
+
export const rateLimitWrapper: ({ rateLimit, rateLimitTimeFrame, customIpHeader }: RateLimitWrapperArgs) => RequestWrapper;
|
|
31
33
|
|
|
32
34
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":";;AAEA,mCAAmC,GAAG,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,OAAO,CAAA;CAAE,CAAC,CAAA;AAE7F,kCAAkC,CAAC,IAAI,EAAE;IACvC,GAAG,EAAE,eAAe,CAAA;IACpB,EAAE,EAAE,MAAM,CAAA;IACV,GAAG,EAAE,cAAc,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,KAAK,IAAI,CAAA;AAEV,wBAAwB,CAAC,IAAI,EAAE;IAC7B,GAAG,EAAE,cAAc,CAAA;IACnB,GAAG,EAAE,eAAe,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;
|
|
1
|
+
{"mappings":";;AAEA,mCAAmC,GAAG,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,OAAO,CAAA;CAAE,CAAC,CAAA;AAE7F,kCAAkC,CAAC,IAAI,EAAE;IACvC,GAAG,EAAE,eAAe,CAAA;IACpB,EAAE,EAAE,MAAM,CAAA;IACV,GAAG,EAAE,cAAc,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,KAAK,IAAI,CAAA;AAEV,wBAAwB,CAAC,IAAI,EAAE;IAC7B,GAAG,EAAE,cAAc,CAAA;IACnB,GAAG,EAAE,eAAe,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,KAAK,eAAe,CAAA;AAErB,mCAAmC;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,CAAA;AEvBD,OAAO,MAAM;;;;CAIZ,CAAA;AAED,OAAO,MAAM,6CAA6C,CAAA;AEC1D,OAAO,MAAM,aAAa,SA0BzB,CAAA;AC5BD,OAAO,MAAM,sEACyC,oBAAoB,KAAG,cAW1E,CAAA","sources":["packages/next/ip-rate-limit/src/src/types.ts","packages/next/ip-rate-limit/src/src/storage.ts","packages/next/ip-rate-limit/src/src/constants.ts","packages/next/ip-rate-limit/src/src/utils.ts","packages/next/ip-rate-limit/src/src/ip-rate-limit.ts","packages/next/ip-rate-limit/src/src/wrapper.ts","packages/next/ip-rate-limit/src/src/index.ts","packages/next/ip-rate-limit/src/index.ts"],"sourcesContent":[null,null,null,null,null,null,null,"export * from './ip-rate-limit'\nexport * from './wrapper'\nexport * from './constants'\nexport * from './types'\n"],"names":[],"version":3,"file":"index.d.ts.map","sourceRoot":"../../../../"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import {isIP as $iZoYP$isIP} from "net";
|
|
2
|
+
|
|
1
3
|
function $parcel$export(e, n, v, s) {
|
|
2
4
|
Object.defineProperty(e, n, {get: v, set: s, enumerable: true, configurable: true});
|
|
3
5
|
}
|
|
@@ -37,6 +39,7 @@ class $a5fb111f145df95d$export$230ecc6ad0ef6558 {
|
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
|
|
42
|
+
|
|
40
43
|
var $1022f9b28fec0213$exports = {};
|
|
41
44
|
|
|
42
45
|
$parcel$export($1022f9b28fec0213$exports, "RATE_LIMIT_HEADERS", () => $1022f9b28fec0213$export$a063967512de3ebf);
|
|
@@ -49,11 +52,21 @@ const $1022f9b28fec0213$export$a063967512de3ebf = {
|
|
|
49
52
|
const $1022f9b28fec0213$export$3f27bc10068ef877 = "API rate limit exceeded for";
|
|
50
53
|
|
|
51
54
|
|
|
52
|
-
const $15f73ba5d70a67f1$export$
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
const $15f73ba5d70a67f1$export$73f7dfe1bf8dc8ad = (ip)=>ip.trim().replace(/^::ffff:/i, "");
|
|
56
|
+
// Get first IP address from a header value or return undefined if invalid
|
|
57
|
+
const $15f73ba5d70a67f1$var$firstHeaderValue = (header)=>{
|
|
58
|
+
const raw = Array.isArray(header) ? header[0] : header;
|
|
59
|
+
const value = raw ? $15f73ba5d70a67f1$export$73f7dfe1bf8dc8ad(raw.split(",")[0]) : undefined;
|
|
60
|
+
return value && (0, $iZoYP$isIP)(value) ? value : undefined;
|
|
61
|
+
};
|
|
62
|
+
const $15f73ba5d70a67f1$export$67218453e01a0be8 = (req, customIpHeader)=>{
|
|
63
|
+
// For custom header setup
|
|
64
|
+
const customIpValue = customIpHeader ? $15f73ba5d70a67f1$var$firstHeaderValue(req.headers[customIpHeader.toLowerCase()]) : undefined;
|
|
65
|
+
// Default CF header
|
|
66
|
+
const cfConnectionIp = $15f73ba5d70a67f1$var$firstHeaderValue(req.headers["cf-connecting-ip"]);
|
|
67
|
+
// Fallback to remote address
|
|
68
|
+
const remoteIp = $15f73ba5d70a67f1$export$73f7dfe1bf8dc8ad(req.socket?.remoteAddress || "0.0.0.0");
|
|
69
|
+
return customIpValue || cfConnectionIp || remoteIp;
|
|
57
70
|
};
|
|
58
71
|
const $15f73ba5d70a67f1$export$d0a20d44a54350ab = ({ id: id, res: res, req: req, retryAfter: retryAfter })=>{
|
|
59
72
|
res.status(429);
|
|
@@ -67,9 +80,9 @@ let $4a980b7982e8368e$var$rateLimitStorage;
|
|
|
67
80
|
const $4a980b7982e8368e$export$122bebc9e141287c = (data)=>{
|
|
68
81
|
if (!$4a980b7982e8368e$var$rateLimitStorage) $4a980b7982e8368e$var$rateLimitStorage = new (0, $a5fb111f145df95d$export$230ecc6ad0ef6558)();
|
|
69
82
|
const now = Date.now() / 1000;
|
|
70
|
-
const { res: res, req: req, limit: limit, timeFrame: timeFrame } = data;
|
|
83
|
+
const { res: res, req: req, limit: limit, timeFrame: timeFrame, customIpHeader: customIpHeader } = data;
|
|
71
84
|
const headers = (0, $1022f9b28fec0213$export$a063967512de3ebf);
|
|
72
|
-
const id = `ip:${(0, $15f73ba5d70a67f1$export$67218453e01a0be8)(req)}`;
|
|
85
|
+
const id = `ip:${(0, $15f73ba5d70a67f1$export$67218453e01a0be8)(req, customIpHeader)}`;
|
|
73
86
|
const time = Math.floor(now / timeFrame);
|
|
74
87
|
const key = `${id}:${time}`;
|
|
75
88
|
const callCount = $4a980b7982e8368e$var$rateLimitStorage.inc(key, data.timeFrame);
|
|
@@ -95,12 +108,13 @@ var $f6440f950b4feda9$exports = {};
|
|
|
95
108
|
|
|
96
109
|
$parcel$export($f6440f950b4feda9$exports, "rateLimitWrapper", () => $f6440f950b4feda9$export$1e3e762f475f853);
|
|
97
110
|
|
|
98
|
-
const $f6440f950b4feda9$export$1e3e762f475f853 = ({ rateLimit: rateLimit, rateLimitTimeFrame: rateLimitTimeFrame })=>async (req, res, next)=>{
|
|
111
|
+
const $f6440f950b4feda9$export$1e3e762f475f853 = ({ rateLimit: rateLimit, rateLimitTimeFrame: rateLimitTimeFrame, customIpHeader: customIpHeader })=>async (req, res, next)=>{
|
|
99
112
|
(0, $4a980b7982e8368e$export$122bebc9e141287c)({
|
|
100
113
|
req: req,
|
|
101
114
|
res: res,
|
|
102
115
|
limit: rateLimit,
|
|
103
|
-
timeFrame: rateLimitTimeFrame
|
|
116
|
+
timeFrame: rateLimitTimeFrame,
|
|
117
|
+
customIpHeader: customIpHeader
|
|
104
118
|
});
|
|
105
119
|
await next?.(req, res, next);
|
|
106
120
|
};
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":"
|
|
1
|
+
{"mappings":"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","sources":["packages/next/ip-rate-limit/src/index.ts","packages/next/ip-rate-limit/src/ip-rate-limit.ts","packages/next/ip-rate-limit/src/storage.ts","packages/next/ip-rate-limit/src/utils.ts","packages/next/ip-rate-limit/src/constants.ts","packages/next/ip-rate-limit/src/wrapper.ts","packages/next/ip-rate-limit/src/types.ts"],"sourcesContent":["export * from './ip-rate-limit'\nexport * from './wrapper'\nexport * from './constants'\nexport * from './types'\n","import { RateLimitStorage } from './storage'\nimport { getIP, rateLimitedResponse } from './utils'\nimport { RATE_LIMIT_HEADERS } from './constants'\nimport { RateLimit } from './types'\n\nlet rateLimitStorage: RateLimitStorage\n\nexport const ipRateLimit: RateLimit = (data) => {\n if (!rateLimitStorage) rateLimitStorage = new RateLimitStorage()\n\n const now = Date.now() / 1000\n\n const { res, req, limit, timeFrame, customIpHeader } = data\n const headers = RATE_LIMIT_HEADERS\n const id = `ip:${getIP(req, customIpHeader)}`\n\n const time = Math.floor(now / timeFrame)\n const key = `${id}:${time}`\n\n const callCount = rateLimitStorage.inc(key, data.timeFrame)\n const remaining = limit - (callCount || 0)\n const reset = (time + 1) * timeFrame\n\n res.setHeader(headers.limit, `${limit}`)\n res.setHeader(headers.remaining, `${remaining < 0 ? 0 : remaining}`)\n res.setHeader(headers.reset, `${reset}`)\n\n if (remaining < 0) {\n const retryAfter = Math.ceil(Math.max(reset - now, 0))\n rateLimitedResponse({ id, res, req, retryAfter })\n }\n\n return res\n}\n","import { RateLimitStorageType } from './types'\n\nexport class RateLimitStorage {\n private _storage: RateLimitStorageType\n private timeoutIds: NodeJS.Timeout[] = []\n\n constructor() {\n this._storage = new Map()\n }\n\n set(key: string, value: number, durationSec: number) {\n this._storage.set(key, { value })\n\n if (this._storage.get(key)?.timeoutId) {\n clearTimeout(this._storage.get(key)?.timeoutId)\n }\n\n // code based on the work with setTimeouts from the code from the nestjs throttling service\n // https://github.com/nestjs/throttler/blob/master/src/throttler.service.ts#L25\n const timeoutId = setTimeout(() => {\n clearTimeout(timeoutId)\n\n this.timeoutIds = this.timeoutIds.filter((id) => id != timeoutId)\n this.delete(key)\n }, durationSec * 1000)\n this.timeoutIds.push(timeoutId)\n }\n\n get(key: string) {\n return this._storage.get(key)?.value\n }\n\n inc(key: string, durationSec: number) {\n if (this._storage.has(key)) {\n this.set(key, (this._storage.get(key)?.value || 0) + 1, durationSec)\n } else {\n this.set(key, 1, durationSec)\n }\n\n return this.get(key)\n }\n delete(key: string) {\n if (this._storage.has(key)) {\n this._storage.delete(key)\n }\n }\n}\n","import { isIP } from 'net'\nimport { NextApiRequest } from 'next'\nimport { ERROR_MESSAGE } from './constants'\nimport { RateLimitedResponse } from './types'\n\n// Normalize IP address by trimming whitespace and removing IPv6 prefix if present\nexport const normalizeIP = (ip: string): string => ip.trim().replace(/^::ffff:/i, '')\n\n// Get first IP address from a header value or return undefined if invalid\nconst firstHeaderValue = (header?: string | string[]): string | undefined => {\n const raw = Array.isArray(header) ? header[0] : header\n const value = raw ? normalizeIP(raw.split(',')[0]) : undefined\n\n return value && isIP(value) ? value : undefined\n}\n\n// now we use headers from \"cloudflare\" and rely on their validity.\n// If there are no \"cloudflare\" headers, we use the \"x-forwarded-for\" header,\n// but keep in mind that these headers can be changed by anyone\n// https://developers.cloudflare.com/fundamentals/get-started/reference/http-request-headers/\nexport const getIP = (req: NextApiRequest, customIpHeader?: string): string => {\n // For custom header setup\n const customIpValue = customIpHeader ? firstHeaderValue(req.headers[customIpHeader.toLowerCase()]) : undefined\n\n // Default CF header\n const cfConnectionIp = firstHeaderValue(req.headers['cf-connecting-ip'])\n\n // Fallback to remote address\n const remoteIp = normalizeIP(req.socket?.remoteAddress || '0.0.0.0')\n\n return customIpValue || cfConnectionIp || remoteIp\n}\n\nexport const rateLimitedResponse: RateLimitedResponse = ({ id, res, req, retryAfter }) => {\n res.status(429)\n if (retryAfter) res.setHeader('Retry-After', retryAfter.toString())\n throw new Error(`${ERROR_MESSAGE} ${id} ${req.url || ''}`)\n}\n","export const RATE_LIMIT_HEADERS = {\n limit: `X-RateLimit-Limit`,\n remaining: `X-RateLimit-Remaining`,\n reset: `X-RateLimit-Reset`,\n}\n\nexport const ERROR_MESSAGE = 'API rate limit exceeded for'\n","import { RequestWrapper } from '@lidofinance/next-api-wrapper'\n\nimport { ipRateLimit } from './ip-rate-limit'\nimport { RateLimitWrapperArgs } from './types'\n\nexport const rateLimitWrapper =\n ({ rateLimit, rateLimitTimeFrame, customIpHeader }: RateLimitWrapperArgs): RequestWrapper =>\n async (req, res, next) => {\n ipRateLimit({\n req,\n res,\n limit: rateLimit,\n timeFrame: rateLimitTimeFrame,\n customIpHeader,\n })\n\n await next?.(req, res, next)\n }\n","import { NextApiResponse, NextApiRequest } from 'next'\n\nexport type RateLimitStorageType = Map<string, { value: number; timeoutId?: NodeJS.Timeout }>\n\nexport type RateLimitedResponse = (data: {\n res: NextApiResponse\n id: string\n req: NextApiRequest\n retryAfter?: number\n}) => void\n\nexport type RateLimit = (data: {\n req: NextApiRequest\n res: NextApiResponse\n limit: number\n timeFrame: number\n customIpHeader?: string\n}) => NextApiResponse\n\nexport type RateLimitWrapperArgs = {\n rateLimit: number\n rateLimitTimeFrame: number\n customIpHeader?: string\n}\n"],"names":[],"version":3,"file":"index.mjs.map","sourceRoot":"../../../../"}
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"url": "https://github.com/lidofinance/warehouse/issues"
|
|
8
8
|
},
|
|
9
9
|
"license": "MIT",
|
|
10
|
-
"version": "0.
|
|
10
|
+
"version": "0.60.0",
|
|
11
11
|
"files": [
|
|
12
12
|
"dist"
|
|
13
13
|
],
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"next": "^12.2.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@lidofinance/config-prettier": "~0.
|
|
38
|
+
"@lidofinance/config-prettier": "~0.60.0",
|
|
39
39
|
"@types/jest": "^29.0.0",
|
|
40
40
|
"@types/node": "^20.0.0",
|
|
41
41
|
"jest": "^29.0.0",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"ts-jest": "^29.0.0"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@lidofinance/next-api-wrapper": "~0.
|
|
46
|
+
"@lidofinance/next-api-wrapper": "~0.60.0"
|
|
47
47
|
},
|
|
48
48
|
"publishConfig": {
|
|
49
49
|
"provenance": true,
|