@lidofinance/next-ip-rate-limit 0.27.0 → 0.28.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 CHANGED
@@ -71,8 +71,9 @@ const $c63185ef25ccfd28$export$67218453e01a0be8 = (req)=>{
71
71
  if (cfConnectionIp) return Array.isArray(cfConnectionIp) ? cfConnectionIp[0] : cfConnectionIp.split(",")[0];
72
72
  return xff ? Array.isArray(xff) ? xff[0] : xff.split(",")[0] : "127.0.0.1";
73
73
  };
74
- const $c63185ef25ccfd28$export$d0a20d44a54350ab = ({ id: id , res: res , req: req })=>{
74
+ const $c63185ef25ccfd28$export$d0a20d44a54350ab = ({ id: id , res: res , req: req , retryAfter: retryAfter })=>{
75
75
  res.status(429);
76
+ if (retryAfter) res.setHeader("Retry-After", retryAfter.toString());
76
77
  throw new Error(`${(0, $54a02752d7f6015e$export$3f27bc10068ef877)} ${id} ${req.url || ""}`);
77
78
  };
78
79
 
@@ -95,7 +96,8 @@ const $abe9c276dfd79488$export$122bebc9e141287c = (data)=>{
95
96
  if (remaining < 0) (0, $c63185ef25ccfd28$export$d0a20d44a54350ab)({
96
97
  id: id,
97
98
  res: res,
98
- req: req
99
+ req: req,
100
+ retryAfter: timeFrame
99
101
  });
100
102
  return res;
101
103
  };
@@ -1 +1 @@
1
- {"mappings":";;;;;;;;;;;;;;;;;;;;;;AEEO,MAAM;IAEH,aAA+B,EAAE,CAAA;IAEzC,aAAc;QACZ,IAAI,CAAC,QAAQ,GAAG,IAAI;IACtB;IAEA,IAAI,GAAW,EAAE,KAAa,EAAE,WAAmB,EAAE;QACnD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK;mBAAE;QAAM;QAE/B,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,WAC1B,aAAa,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM;QAGvC,2FAA2F;QAC3F,+EAA+E;QAC/E,MAAM,YAAY,WAAW,IAAM;YACjC,aAAa;YAEb,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,KAAO,MAAM;YACvD,IAAI,CAAC,MAAM,CAAC;QACd,GAAG,cAAc;QACjB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;IACvB;IAEA,IAAI,GAAW,EAAE;QACf,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM;IACjC;IAEA,IAAI,GAAW,EAAE,WAAmB,EAAE;QACpC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MACpB,IAAI,CAAC,GAAG,CAAC,KAAK,AAAC,CAAA,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,SAAS,CAAA,IAAK,GAAG;aAExD,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG;QAGnB,OAAO,IAAI,CAAC,GAAG,CAAC;IAClB;IACA,OAAO,GAAW,EAAE;QAClB,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MACpB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;IAEzB;AACF;;AD9CA;;;;;AGAO,MAAM,4CAAqB;IAChC,OAAO,CAAC,iBAAiB,CAAC;IAC1B,WAAW,CAAC,qBAAqB,CAAC;IAClC,OAAO,CAAC,iBAAiB,CAAC;AAC5B;AAEO,MAAM,4CAAgB;;ADN7B;AAQO,MAAM,4CAAQ,CAAC,MAAwB;IAC5C,MAAM,iBAAiB,IAAI,OAAO,CAAC,mBAAmB;IACtD,MAAM,MAAM,IAAI,OAAO,CAAC,kBAAkB;IAE1C,IAAI,gBAAgB,OAAO,MAAM,OAAO,CAAC,kBAAkB,cAAc,CAAC,EAAE,GAAG,eAAe,KAAK,CAAC,IAAI,CAAC,EAAE;IAE3G,OAAO,MAAO,MAAM,OAAO,CAAC,OAAO,GAAG,CAAC,EAAE,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,GAAI,WAAW;AAC9E;AAEO,MAAM,4CAA2C,CAAC,MAAE,GAAE,OAAE,IAAG,OAAE,IAAG,EAAE,GAAK;IAC5E,IAAI,MAAM,CAAC;IAEX,MAAM,IAAI,MAAM,CAAC,EAAE,CAAA,GAAA,yCAAa,AAAD,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,CAAC,EAAC;AAC5D;;;;AFhBA,IAAI;AAEG,MAAM,4CAAyB,CAAC,OAAS;IAC9C,IAAI,CAAC,wCAAkB,yCAAmB,IAAI,CAAA,GAAA,yCAAgB,AAAD;IAE7D,MAAM,OAAE,IAAG,OAAE,IAAG,SAAE,MAAK,aAAE,UAAS,EAAE,GAAG;IACvC,MAAM,UAAU,CAAA,GAAA,yCAAkB,AAAD;IACjC,MAAM,KAAK,CAAC,GAAG,EAAE,CAAA,GAAA,yCAAK,AAAD,EAAE,KAAK,CAAC;IAE7B,MAAM,OAAO,KAAK,KAAK,CAAC,KAAK,GAAG,KAAK,OAAO;IAC5C,MAAM,MAAM,CAAC,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC;IAE3B,MAAM,YAAY,uCAAiB,GAAG,CAAC,KAAK,KAAK,SAAS;IAC1D,MAAM,YAAY,QAAS,CAAA,aAAa,CAAA;IACxC,MAAM,QAAQ,AAAC,CAAA,OAAO,CAAA,IAAK;IAE3B,IAAI,SAAS,CAAC,QAAQ,KAAK,EAAE,CAAC,EAAE,MAAM,CAAC;IACvC,IAAI,SAAS,CAAC,QAAQ,SAAS,EAAE,CAAC,EAAE,YAAY,IAAI,IAAI,SAAS,CAAC,CAAC;IACnE,IAAI,SAAS,CAAC,QAAQ,KAAK,EAAE,CAAC,EAAE,MAAM,CAAC;IAEvC,IAAI,YAAY,GAAG,CAAA,GAAA,yCAAkB,EAAE;YAAE;aAAI;aAAK;IAAI;IAEtD,OAAO;AACT;;AD5BA;;;;AKAA;AAKO,MAAM,2CACX,CAAC,aAAE,UAAS,sBAAE,mBAAkB,EAAwB,GACxD,OAAO,KAAK,KAAK,OAAS;QACxB,CAAA,GAAA,yCAAW,AAAD,EAAE;iBACV;iBACA;YACA,OAAO;YACP,WAAW;QACb;QAEA,MAAM,OAAO,KAAK,KAAK;IACzB;;;;;;","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 { res, req, limit, timeFrame } = data\n const headers = RATE_LIMIT_HEADERS\n const id = `ip:${getIP(req)}`\n\n const time = Math.floor(Date.now() / 1000 / 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) rateLimitedResponse({ id, res, req })\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 { NextApiRequest } from 'next'\nimport { ERROR_MESSAGE } from './constants'\nimport { RateLimitedResponse } from './types'\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) => {\n const cfConnectionIp = req.headers['cf_connecting_ip']\n const xff = req.headers['x-forwarded-for']\n\n if (cfConnectionIp) return Array.isArray(cfConnectionIp) ? cfConnectionIp[0] : cfConnectionIp.split(',')[0]\n\n return xff ? (Array.isArray(xff) ? xff[0] : xff.split(',')[0]) : '127.0.0.1'\n}\n\nexport const rateLimitedResponse: RateLimitedResponse = ({ id, res, req }) => {\n res.status(429)\n\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 }: RateLimitWrapperArgs): RequestWrapper =>\n async (req, res, next) => {\n ipRateLimit({\n req,\n res,\n limit: rateLimit,\n timeFrame: rateLimitTimeFrame,\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: { res: NextApiResponse; id: string; req: NextApiRequest }) => void\n\nexport type RateLimit = (data: {\n req: NextApiRequest\n res: NextApiResponse\n limit: number\n timeFrame: number\n}) => NextApiResponse\n\nexport type RateLimitWrapperArgs = {\n rateLimit: number\n rateLimitTimeFrame: number\n}\n"],"names":[],"version":3,"file":"index.cjs.map","sourceRoot":"../../../../"}
1
+ {"mappings":";;;;;;;;;;;;;;;;;;;;;;AEEO,MAAM;IAEH,aAA+B,EAAE,CAAA;IAEzC,aAAc;QACZ,IAAI,CAAC,QAAQ,GAAG,IAAI;IACtB;IAEA,IAAI,GAAW,EAAE,KAAa,EAAE,WAAmB,EAAE;QACnD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK;mBAAE;QAAM;QAE/B,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,WAC1B,aAAa,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM;QAGvC,2FAA2F;QAC3F,+EAA+E;QAC/E,MAAM,YAAY,WAAW,IAAM;YACjC,aAAa;YAEb,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,KAAO,MAAM;YACvD,IAAI,CAAC,MAAM,CAAC;QACd,GAAG,cAAc;QACjB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;IACvB;IAEA,IAAI,GAAW,EAAE;QACf,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM;IACjC;IAEA,IAAI,GAAW,EAAE,WAAmB,EAAE;QACpC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MACpB,IAAI,CAAC,GAAG,CAAC,KAAK,AAAC,CAAA,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,SAAS,CAAA,IAAK,GAAG;aAExD,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG;QAGnB,OAAO,IAAI,CAAC,GAAG,CAAC;IAClB;IACA,OAAO,GAAW,EAAE;QAClB,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MACpB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;IAEzB;AACF;;AD9CA;;;;;AGAO,MAAM,4CAAqB;IAChC,OAAO,CAAC,iBAAiB,CAAC;IAC1B,WAAW,CAAC,qBAAqB,CAAC;IAClC,OAAO,CAAC,iBAAiB,CAAC;AAC5B;AAEO,MAAM,4CAAgB;;ADN7B;AAQO,MAAM,4CAAQ,CAAC,MAAwB;IAC5C,MAAM,iBAAiB,IAAI,OAAO,CAAC,mBAAmB;IACtD,MAAM,MAAM,IAAI,OAAO,CAAC,kBAAkB;IAE1C,IAAI,gBAAgB,OAAO,MAAM,OAAO,CAAC,kBAAkB,cAAc,CAAC,EAAE,GAAG,eAAe,KAAK,CAAC,IAAI,CAAC,EAAE;IAE3G,OAAO,MAAO,MAAM,OAAO,CAAC,OAAO,GAAG,CAAC,EAAE,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,GAAI,WAAW;AAC9E;AAEO,MAAM,4CAA2C,CAAC,MAAE,GAAE,OAAE,IAAG,OAAE,IAAG,cAAE,WAAU,EAAE,GAAK;IACxF,IAAI,MAAM,CAAC;IACX,IAAI,YAAY,IAAI,SAAS,CAAC,eAAe,WAAW,QAAQ;IAChE,MAAM,IAAI,MAAM,CAAC,EAAE,CAAA,GAAA,yCAAa,AAAD,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,CAAC,EAAC;AAC5D;;;;AFhBA,IAAI;AAEG,MAAM,4CAAyB,CAAC,OAAS;IAC9C,IAAI,CAAC,wCAAkB,yCAAmB,IAAI,CAAA,GAAA,yCAAgB,AAAD;IAE7D,MAAM,OAAE,IAAG,OAAE,IAAG,SAAE,MAAK,aAAE,UAAS,EAAE,GAAG;IACvC,MAAM,UAAU,CAAA,GAAA,yCAAkB,AAAD;IACjC,MAAM,KAAK,CAAC,GAAG,EAAE,CAAA,GAAA,yCAAK,AAAD,EAAE,KAAK,CAAC;IAE7B,MAAM,OAAO,KAAK,KAAK,CAAC,KAAK,GAAG,KAAK,OAAO;IAC5C,MAAM,MAAM,CAAC,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC;IAE3B,MAAM,YAAY,uCAAiB,GAAG,CAAC,KAAK,KAAK,SAAS;IAC1D,MAAM,YAAY,QAAS,CAAA,aAAa,CAAA;IACxC,MAAM,QAAQ,AAAC,CAAA,OAAO,CAAA,IAAK;IAE3B,IAAI,SAAS,CAAC,QAAQ,KAAK,EAAE,CAAC,EAAE,MAAM,CAAC;IACvC,IAAI,SAAS,CAAC,QAAQ,SAAS,EAAE,CAAC,EAAE,YAAY,IAAI,IAAI,SAAS,CAAC,CAAC;IACnE,IAAI,SAAS,CAAC,QAAQ,KAAK,EAAE,CAAC,EAAE,MAAM,CAAC;IAEvC,IAAI,YAAY,GAAG,CAAA,GAAA,yCAAkB,EAAE;YAAE;aAAI;aAAK;QAAK,YAAY;IAAU;IAE7E,OAAO;AACT;;AD5BA;;;;AKAA;AAKO,MAAM,2CACX,CAAC,aAAE,UAAS,sBAAE,mBAAkB,EAAwB,GACxD,OAAO,KAAK,KAAK,OAAS;QACxB,CAAA,GAAA,yCAAW,AAAD,EAAE;iBACV;iBACA;YACA,OAAO;YACP,WAAW;QACb;QAEA,MAAM,OAAO,KAAK,KAAK;IACzB;;;;;;","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 { res, req, limit, timeFrame } = data\n const headers = RATE_LIMIT_HEADERS\n const id = `ip:${getIP(req)}`\n\n const time = Math.floor(Date.now() / 1000 / 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) rateLimitedResponse({ id, res, req, retryAfter: timeFrame })\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 { NextApiRequest } from 'next'\nimport { ERROR_MESSAGE } from './constants'\nimport { RateLimitedResponse } from './types'\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) => {\n const cfConnectionIp = req.headers['cf_connecting_ip']\n const xff = req.headers['x-forwarded-for']\n\n if (cfConnectionIp) return Array.isArray(cfConnectionIp) ? cfConnectionIp[0] : cfConnectionIp.split(',')[0]\n\n return xff ? (Array.isArray(xff) ? xff[0] : xff.split(',')[0]) : '127.0.0.1'\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 }: RateLimitWrapperArgs): RequestWrapper =>\n async (req, res, next) => {\n ipRateLimit({\n req,\n res,\n limit: rateLimit,\n timeFrame: rateLimitTimeFrame,\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}) => NextApiResponse\n\nexport type RateLimitWrapperArgs = {\n rateLimit: number\n rateLimitTimeFrame: number\n}\n"],"names":[],"version":3,"file":"index.cjs.map","sourceRoot":"../../../../"}
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export type RateLimitedResponse = (data: {
8
8
  res: NextApiResponse;
9
9
  id: string;
10
10
  req: NextApiRequest;
11
+ retryAfter?: number;
11
12
  }) => void;
12
13
  export type RateLimit = (data: {
13
14
  req: NextApiRequest;
@@ -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;IAAE,GAAG,EAAE,eAAe,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,cAAc,CAAA;CAAE,KAAK,IAAI,CAAA;AAE3G,wBAAwB,CAAC,IAAI,EAAE;IAC7B,GAAG,EAAE,cAAc,CAAA;IACnB,GAAG,EAAE,eAAe,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;CAClB,KAAK,eAAe,CAAA;AAErB,mCAAmC;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB,kBAAkB,EAAE,MAAM,CAAA;CAC3B,CAAA;AEhBD,OAAO,MAAM;;;;CAIZ,CAAA;AAED,OAAO,MAAM,6CAA6C,CAAA;AEC1D,OAAO,MAAM,aAAa,SAqBzB,CAAA;ACvBD,OAAO,MAAM,sDACyB,oBAAoB,KAAG,cAU1D,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":"../../../../"}
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;CAClB,KAAK,eAAe,CAAA;AAErB,mCAAmC;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB,kBAAkB,EAAE,MAAM,CAAA;CAC3B,CAAA;AErBD,OAAO,MAAM;;;;CAIZ,CAAA;AAED,OAAO,MAAM,6CAA6C,CAAA;AEC1D,OAAO,MAAM,aAAa,SAqBzB,CAAA;ACvBD,OAAO,MAAM,sDACyB,oBAAoB,KAAG,cAU1D,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
@@ -55,8 +55,9 @@ const $15f73ba5d70a67f1$export$67218453e01a0be8 = (req)=>{
55
55
  if (cfConnectionIp) return Array.isArray(cfConnectionIp) ? cfConnectionIp[0] : cfConnectionIp.split(",")[0];
56
56
  return xff ? Array.isArray(xff) ? xff[0] : xff.split(",")[0] : "127.0.0.1";
57
57
  };
58
- const $15f73ba5d70a67f1$export$d0a20d44a54350ab = ({ id: id , res: res , req: req })=>{
58
+ const $15f73ba5d70a67f1$export$d0a20d44a54350ab = ({ id: id , res: res , req: req , retryAfter: retryAfter })=>{
59
59
  res.status(429);
60
+ if (retryAfter) res.setHeader("Retry-After", retryAfter.toString());
60
61
  throw new Error(`${(0, $1022f9b28fec0213$export$3f27bc10068ef877)} ${id} ${req.url || ""}`);
61
62
  };
62
63
 
@@ -79,7 +80,8 @@ const $4a980b7982e8368e$export$122bebc9e141287c = (data)=>{
79
80
  if (remaining < 0) (0, $15f73ba5d70a67f1$export$d0a20d44a54350ab)({
80
81
  id: id,
81
82
  res: res,
82
- req: req
83
+ req: req,
84
+ retryAfter: timeFrame
83
85
  });
84
86
  return res;
85
87
  };
@@ -1 +1 @@
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 { res, req, limit, timeFrame } = data\n const headers = RATE_LIMIT_HEADERS\n const id = `ip:${getIP(req)}`\n\n const time = Math.floor(Date.now() / 1000 / 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) rateLimitedResponse({ id, res, req })\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 { NextApiRequest } from 'next'\nimport { ERROR_MESSAGE } from './constants'\nimport { RateLimitedResponse } from './types'\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) => {\n const cfConnectionIp = req.headers['cf_connecting_ip']\n const xff = req.headers['x-forwarded-for']\n\n if (cfConnectionIp) return Array.isArray(cfConnectionIp) ? cfConnectionIp[0] : cfConnectionIp.split(',')[0]\n\n return xff ? (Array.isArray(xff) ? xff[0] : xff.split(',')[0]) : '127.0.0.1'\n}\n\nexport const rateLimitedResponse: RateLimitedResponse = ({ id, res, req }) => {\n res.status(429)\n\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 }: RateLimitWrapperArgs): RequestWrapper =>\n async (req, res, next) => {\n ipRateLimit({\n req,\n res,\n limit: rateLimit,\n timeFrame: rateLimitTimeFrame,\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: { res: NextApiResponse; id: string; req: NextApiRequest }) => void\n\nexport type RateLimit = (data: {\n req: NextApiRequest\n res: NextApiResponse\n limit: number\n timeFrame: number\n}) => NextApiResponse\n\nexport type RateLimitWrapperArgs = {\n rateLimit: number\n rateLimitTimeFrame: number\n}\n"],"names":[],"version":3,"file":"index.mjs.map","sourceRoot":"../../../../"}
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 { res, req, limit, timeFrame } = data\n const headers = RATE_LIMIT_HEADERS\n const id = `ip:${getIP(req)}`\n\n const time = Math.floor(Date.now() / 1000 / 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) rateLimitedResponse({ id, res, req, retryAfter: timeFrame })\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 { NextApiRequest } from 'next'\nimport { ERROR_MESSAGE } from './constants'\nimport { RateLimitedResponse } from './types'\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) => {\n const cfConnectionIp = req.headers['cf_connecting_ip']\n const xff = req.headers['x-forwarded-for']\n\n if (cfConnectionIp) return Array.isArray(cfConnectionIp) ? cfConnectionIp[0] : cfConnectionIp.split(',')[0]\n\n return xff ? (Array.isArray(xff) ? xff[0] : xff.split(',')[0]) : '127.0.0.1'\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 }: RateLimitWrapperArgs): RequestWrapper =>\n async (req, res, next) => {\n ipRateLimit({\n req,\n res,\n limit: rateLimit,\n timeFrame: rateLimitTimeFrame,\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}) => NextApiResponse\n\nexport type RateLimitWrapperArgs = {\n rateLimit: number\n rateLimitTimeFrame: number\n}\n"],"names":[],"version":3,"file":"index.mjs.map","sourceRoot":"../../../../"}
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "IP rate limit for next server api requests",
4
4
  "repository": "git@github.com:lidofinance/warehouse.git",
5
5
  "license": "MIT",
6
- "version": "0.27.0",
6
+ "version": "0.28.0",
7
7
  "files": [
8
8
  "dist"
9
9
  ],
@@ -25,7 +25,7 @@
25
25
  "next": "^12.2.0"
26
26
  },
27
27
  "devDependencies": {
28
- "@lidofinance/config-prettier": "~0.27.0",
28
+ "@lidofinance/config-prettier": "~0.28.0",
29
29
  "@types/jest": "^28.0.0",
30
30
  "@types/node": "^18.11.2",
31
31
  "jest": "^28.0.0",
@@ -33,6 +33,6 @@
33
33
  "ts-jest": "^28.0.0"
34
34
  },
35
35
  "dependencies": {
36
- "@lidofinance/next-api-wrapper": "~0.27.0"
36
+ "@lidofinance/next-api-wrapper": "~0.28.0"
37
37
  }
38
38
  }