yuken пре 3 година
родитељ
комит
5e1921e74b

+ 24 - 42
packages/app/src/server/middlewares/api-rate-limiter.ts

@@ -1,74 +1,56 @@
-import { NextFunction, Request, Response } from 'express';
+import e, { NextFunction, Request, Response } from 'express';
+import { RateLimiterMemory } from 'rate-limiter-flexible';
 
 import loggerFactory from '~/utils/logger';
 
+import getCustomApiRateLimit from '../util/getCustomApiRateLimit';
 
-const logger = loggerFactory('growi:middleware:api-rate-limit');
 
+const logger = loggerFactory('growi:middleware:api-rate-limit');
 
+// e.g.
 // API_RATE_LIMIT_010_FOO_ENDPOINT=/_api/v3/foo
 // API_RATE_LIMIT_010_FOO_METHODS=GET,POST
 // API_RATE_LIMIT_010_FOO_CONSUME_POINTS=10
 
-module.exports = (rateLimiter, defaultPoints: number) => {
+const consumePoints = async(rateLimiter: RateLimiterMemory, key: string, points: number, next: NextFunction) => {
+  await rateLimiter.consume(key, points)
+    .then(() => {
+      next();
+    })
+    .catch(() => {
+      logger.error(`too many request at ${key}`);
+    });
+};
+
+module.exports = (rateLimiter: RateLimiterMemory, defaultPoints: number) => {
 
   return async(req: Request, res: Response, next: NextFunction) => {
 
+    // e.g. /_api/v3/page/info?pageId=628c64f2b78c8d7e084ee979 => /_api/v3/page/info
     const endpoint = req.url.replace(/\?.*$/, '');
-    const key = req.ip + endpoint;
+    const key = req.ip + req.url;
 
-    const consumePoints = async(points: number = defaultPoints) => {
-      await rateLimiter.consume(key, points)
-        .then(() => {
-          next();
-        })
-        .catch(() => {
-          logger.error(`too many request at ${endpoint}`);
-        });
-    };
+    const envVarDic = process.env;
 
     // pick up API_RATE_LIMIT_*_ENDPOINT from ENV
-    const apiRateEndpointKeys = Object.keys(process.env).filter((key) => {
+    const apiRateEndpointKeys = Object.keys(envVarDic).filter((key) => {
       const endpointRegExp = /^API_RATE_LIMIT_.*_ENDPOINT/;
       return endpointRegExp.test(key);
     });
 
     const matchedEndpointKeys = apiRateEndpointKeys.filter((key) => {
-      return process.env[key] === endpoint;
+      return envVarDic[key] === endpoint;
     });
 
     if (matchedEndpointKeys.length === 0) {
-      await consumePoints();
-      return;
-    }
-
-    let prioritizedTarget: [string, string] | null = null; // priprity and keyword
-    matchedEndpointKeys.forEach((key) => {
-      const target = key.replace('API_RATE_LIMIT_', '').replace('_ENDPOINT', '');
-      const priority = target.split('_')[0];
-      const keyword = target.split('_')[1];
-      if (prioritizedTarget === null || Number(priority) > Number(prioritizedTarget[0])) {
-        prioritizedTarget = [priority, keyword];
-      }
-    });
-
-    if (prioritizedTarget === null) {
-      await consumePoints();
-      return;
-    }
-
-    const targetMethodsKey = `API_RATE_LIMIT_${prioritizedTarget[0]}_${prioritizedTarget[1]}_METHODS`;
-    const targetConsumePointsKey = `API_RATE_LIMIT_${prioritizedTarget[0]}_${prioritizedTarget[1]}_CONSUME_POINTS`;
-
-    const targetMethods = process.env[targetMethodsKey];
-    if (targetMethods === undefined || !targetMethods.includes(req.method)) {
-      await consumePoints();
+      await consumePoints(rateLimiter, key, defaultPoints, next);
       return;
     }
 
-    const customizedConsumePoints = process.env[targetConsumePointsKey];
+    const customizedConsumePoints = getCustomApiRateLimit(matchedEndpointKeys, req.method);
 
-    await consumePoints(Number(customizedConsumePoints));
+    await consumePoints(rateLimiter, key, customizedConsumePoints ?? defaultPoints, next);
     return;
   };
 };

+ 1 - 1
packages/app/src/server/routes/index.js

@@ -1,4 +1,5 @@
 import express from 'express';
+import { RateLimiterMemory } from 'rate-limiter-flexible';
 
 import apiV1FormValidator from '../middlewares/apiv1-form-validator';
 import injectResetOrderByTokenMiddleware from '../middlewares/inject-reset-order-by-token-middleware';
@@ -17,7 +18,6 @@ import * as userActivation from './user-activation';
 
 const multer = require('multer');
 const autoReap = require('multer-autoreap');
-const { RateLimiterMemory } = require('rate-limiter-flexible');
 
 const opts = {
   points: 100, // set default value

+ 33 - 0
packages/app/src/server/util/getCustomApiRateLimit.ts

@@ -0,0 +1,33 @@
+const getCustomApiRateLimit = (matchedEndpointKeys: string[], method: string): number | null => {
+
+  let prioritizedTarget: [string, string] | null = null; // priprity and keyword
+  matchedEndpointKeys.forEach((key) => {
+    const target = key.replace('API_RATE_LIMIT_', '').replace('_ENDPOINT', '');
+    const priority = target.split('_')[0];
+    const keyword = target.split('_')[1];
+    if (prioritizedTarget === null || Number(priority) > Number(prioritizedTarget[0])) {
+      prioritizedTarget = [priority, keyword];
+    }
+  });
+
+  if (prioritizedTarget === null) {
+    return null;
+  }
+
+  const envVarDic = process.env;
+
+  const targetMethodsKey = `API_RATE_LIMIT_${prioritizedTarget[0]}_${prioritizedTarget[1]}_METHODS`;
+  const targetConsumePointsKey = `API_RATE_LIMIT_${prioritizedTarget[0]}_${prioritizedTarget[1]}_CONSUME_POINTS`;
+
+  const targetMethods = envVarDic[targetMethodsKey];
+  if (targetMethods === undefined || !targetMethods.includes(method)) {
+    return null;
+  }
+
+  const customizedConsumePoints = envVarDic[targetConsumePointsKey];
+
+  return Number(customizedConsumePoints);
+
+};
+
+export default getCustomApiRateLimit;