Просмотр исходного кода

Separate rate-limiter-flexible client and wrapper in separate files

Shun Miyazawa 1 год назад
Родитель
Сommit
1d570c1b6c

+ 27 - 0
apps/app/src/features/rate-limiter/middleware/consume-points.ts

@@ -0,0 +1,27 @@
+import { type RateLimiterMongo, type RateLimiterRes } from 'rate-limiter-flexible';
+
+import { DEFAULT_MAX_REQUESTS, type IApiRateLimitConfig } from '../config';
+
+export const consumePoints = async(
+    rateLimiter: RateLimiterMongo, method: string, key: string | null, customizedConfig?: IApiRateLimitConfig, maxRequestsMultiplier?: number,
+): Promise<RateLimiterRes | undefined> => {
+  if (key == null) {
+    return;
+  }
+
+  let maxRequests = DEFAULT_MAX_REQUESTS;
+
+  // use customizedConfig
+  if (customizedConfig != null && (customizedConfig.method.includes(method) || customizedConfig.method === 'ALL')) {
+    maxRequests = customizedConfig.maxRequests;
+  }
+
+  // multiply
+  if (maxRequestsMultiplier != null) {
+    maxRequests *= maxRequestsMultiplier;
+  }
+
+  rateLimiter.points = maxRequests;
+  const rateLimiterRes = await rateLimiter.consume(key, 1);
+  return rateLimiterRes;
+};

+ 6 - 5
apps/app/src/features/rate-limiter/middleware/factory.integ.ts

@@ -1,15 +1,17 @@
 import { faker } from '@faker-js/faker';
 
+import { consumePoints } from './consume-points';
+
 const testRateLimitErrorWhenExceedingMaxRequests = async(method: string, key: string, maxRequests: number): Promise<void> => {
-  // dynamic import is used because rateLimiterMongo needs to be initialized after connecting to DB
-  // Issue: https://github.com/animir/node-rate-limiter-flexible/issues/216
-  const { _consumePoints } = await import('./factory');
+  // // dynamic import is used because rateLimiterMongo needs to be initialized after connecting to DB
+  // // Issue: https://github.com/animir/node-rate-limiter-flexible/issues/216
+  const { rateLimiter } = await import('./rate-limiter-mongo-client');
   let count = 0;
   try {
     for (let i = 1; i <= maxRequests + 1; i++) {
       count += 1;
       // eslint-disable-next-line no-await-in-loop
-      const res = await _consumePoints(method, key, { method, maxRequests });
+      const res = await consumePoints(rateLimiter, method, key, { method, maxRequests });
       if (count === maxRequests) {
         // Expect consumedPoints to be equal to maxRequest when maxRequest is reached
         expect(res?.consumedPoints).toBe(maxRequests);
@@ -24,7 +26,6 @@ const testRateLimitErrorWhenExceedingMaxRequests = async(method: string, key: st
   catch (err) {
     // Expect not to exceed maxRequest
     expect(err.message).not.toBe('Exception occurred');
-
     // Expect rate limit error at maxRequest + 1
     expect(count).toBe(maxRequests + 1);
   }

+ 6 - 37
apps/app/src/features/rate-limiter/middleware/factory.ts

@@ -1,16 +1,15 @@
 import type { IUserHasId } from '@growi/core';
 import type { Handler, Request } from 'express';
 import md5 from 'md5';
-import { connection } from 'mongoose';
-import { type IRateLimiterMongoOptions, type RateLimiterRes, RateLimiterMongo } from 'rate-limiter-flexible';
+import { type RateLimiterRes } from 'rate-limiter-flexible';
 
 import loggerFactory from '~/utils/logger';
 
-import {
-  DEFAULT_DURATION_SEC, DEFAULT_MAX_REQUESTS, DEFAULT_USERS_PER_IP_PROSPECTION, type IApiRateLimitConfig,
-} from '../config';
+import { DEFAULT_USERS_PER_IP_PROSPECTION, type IApiRateLimitConfig } from '../config';
 import { generateApiRateLimitConfig } from '../utils/config-generator';
 
+import { consumePoints } from './consume-points';
+import { rateLimiter } from './rate-limiter-mongo-client';
 
 const logger = loggerFactory('growi:middleware:api-rate-limit');
 
@@ -19,12 +18,6 @@ const logger = loggerFactory('growi:middleware:api-rate-limit');
 // API_RATE_LIMIT_010_FOO_METHODS=GET,POST
 // API_RATE_LIMIT_010_FOO_MAX_REQUESTS=10
 
-const opts: IRateLimiterMongoOptions = {
-  storeClient: connection,
-  duration: DEFAULT_DURATION_SEC, // set default value
-};
-const rateLimiter = new RateLimiterMongo(opts);
-
 // generate ApiRateLimitConfig for api rate limiter
 const apiRateLimitConfig = generateApiRateLimitConfig();
 const configWithoutRegExp = apiRateLimitConfig.withoutRegExp;
@@ -34,30 +27,6 @@ const keysWithRegExp = Object.keys(configWithRegExp).map(key => new RegExp(`^${k
 const valuesWithRegExp = Object.values(configWithRegExp);
 
 
-export const _consumePoints = async(
-    method: string, key: string | null, customizedConfig?: IApiRateLimitConfig, maxRequestsMultiplier?: number,
-): Promise<RateLimiterRes | undefined> => {
-  if (key == null) {
-    return;
-  }
-
-  let maxRequests = DEFAULT_MAX_REQUESTS;
-
-  // use customizedConfig
-  if (customizedConfig != null && (customizedConfig.method.includes(method) || customizedConfig.method === 'ALL')) {
-    maxRequests = customizedConfig.maxRequests;
-  }
-
-  // multiply
-  if (maxRequestsMultiplier != null) {
-    maxRequests *= maxRequestsMultiplier;
-  }
-
-  rateLimiter.points = maxRequests;
-  const rateLimiterRes = await rateLimiter.consume(key, 1);
-  return rateLimiterRes;
-};
-
 /**
  * consume per user per endpoint
  * @param method
@@ -66,7 +35,7 @@ export const _consumePoints = async(
  * @returns
  */
 const consumePointsByUser = async(method: string, key: string | null, customizedConfig?: IApiRateLimitConfig): Promise<RateLimiterRes | undefined> => {
-  return _consumePoints(method, key, customizedConfig);
+  return consumePoints(rateLimiter, method, key, customizedConfig);
 };
 
 /**
@@ -78,7 +47,7 @@ const consumePointsByUser = async(method: string, key: string | null, customized
  */
 const consumePointsByIp = async(method: string, key: string | null, customizedConfig?: IApiRateLimitConfig): Promise<RateLimiterRes | undefined> => {
   const maxRequestsMultiplier = customizedConfig?.usersPerIpProspection ?? DEFAULT_USERS_PER_IP_PROSPECTION;
-  return _consumePoints(method, key, customizedConfig, maxRequestsMultiplier);
+  return consumePoints(rateLimiter, method, key, customizedConfig, maxRequestsMultiplier);
 };
 
 

+ 11 - 0
apps/app/src/features/rate-limiter/middleware/rate-limiter-mongo-client.ts

@@ -0,0 +1,11 @@
+import { connection } from 'mongoose';
+import { type IRateLimiterMongoOptions, RateLimiterMongo } from 'rate-limiter-flexible';
+
+import { DEFAULT_DURATION_SEC } from '../config';
+
+const opts: IRateLimiterMongoOptions = {
+  storeClient: connection,
+  duration: DEFAULT_DURATION_SEC, // set default value
+};
+
+export const rateLimiter = new RateLimiterMongo(opts);