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

Merge pull request #5935 from weseek/feat/96274-96275-connect-mongo-and-refactor-config-system

feat: 96274 96275 connect to mongo and refactor config system
yuken 3 лет назад
Родитель
Сommit
34832c1dcb

+ 1 - 1
packages/app/src/server/interfaces/api-rate-limit-config.ts

@@ -1,6 +1,6 @@
 export type IApiRateLimitConfig = {
   [endpoint: string]: {
     method: string,
-    consumePoints: number
+    maxRequests: number
   }
 }

+ 31 - 22
packages/app/src/server/middlewares/api-rate-limiter.ts

@@ -1,5 +1,7 @@
 import { NextFunction, Request, Response } from 'express';
-import { RateLimiterMemory } from 'rate-limiter-flexible';
+import md5 from 'md5';
+import mongoose from 'mongoose';
+import { RateLimiterMongo } from 'rate-limiter-flexible';
 
 import loggerFactory from '~/utils/logger';
 
@@ -8,26 +10,27 @@ import { generateApiRateLimitConfig } from '../util/generateApiRateLimitConfig';
 
 const logger = loggerFactory('growi:middleware:api-rate-limit');
 
+// config sample
+// API_RATE_LIMIT_010_FOO_ENDPOINT=/_api/v3/foo
+// API_RATE_LIMIT_010_FOO_METHODS=GET,POST
+// API_RATE_LIMIT_010_FOO_MAX_REQUESTS=10
+
 const defaultMaxPoints = 100;
-const defaultConsumePoints = 10;
+const defaultMaxRequests = 10;
 const defaultDuration = 1;
 const opts = {
+  storeClient: mongoose.connection,
   points: defaultMaxPoints, // set default value
   duration: defaultDuration, // set default value
 };
-const rateLimiter = new RateLimiterMemory(opts);
+const rateLimiter = new RateLimiterMongo(opts);
 
 // generate ApiRateLimitConfig for api rate limiter
 const apiRateLimitConfig = generateApiRateLimitConfig();
 
-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}`);
-    });
+const consumePoints = async(rateLimiter: RateLimiterMongo, key: string, points: number) => {
+  const consumePoints = defaultMaxPoints / points;
+  await rateLimiter.consume(key, consumePoints);
 };
 
 module.exports = () => {
@@ -35,21 +38,27 @@ module.exports = () => {
   return async(req: Request, res: Response, next: NextFunction) => {
 
     const endpoint = req.path;
-    const key = req.ip + endpoint;
+    const key = md5(req.ip + endpoint);
 
     const customizedConfig = apiRateLimitConfig[endpoint];
 
-    if (customizedConfig === undefined) {
-      await consumePoints(rateLimiter, key, defaultConsumePoints, next);
-      return;
-    }
+    try {
+      if (customizedConfig === undefined) {
+        await consumePoints(rateLimiter, key, defaultMaxRequests);
+        return next();
+      }
 
-    if (customizedConfig.method.includes(req.method) || customizedConfig.method === 'ALL') {
-      await consumePoints(rateLimiter, key, customizedConfig.consumePoints, next);
-      return;
-    }
+      if (customizedConfig.method.includes(req.method) || customizedConfig.method === 'ALL') {
+        await consumePoints(rateLimiter, key, customizedConfig.maxRequests);
+        return next();
+      }
 
-    await consumePoints(rateLimiter, key, defaultConsumePoints, next);
-    return;
+      await consumePoints(rateLimiter, key, defaultMaxRequests);
+      return next();
+    }
+    catch {
+      logger.error(`${req.ip}: too many request at ${endpoint}`);
+      return res.sendStatus(429);
+    }
   };
 };

+ 4 - 4
packages/app/src/server/util/generateApiRateLimitConfig.ts

@@ -16,15 +16,15 @@ const generateApiRateLimitConfigFromEndpoint = (envVar: NodeJS.ProcessEnv, endpo
 
     const target = getTargetFromKey(key);
     const method = envVar[`API_RATE_LIMIT_${target}_METHODS`] ?? 'ALL';
-    const consumePoints = Number(envVar[`API_RATE_LIMIT_${target}_CONSUME_POINTS`]);
+    const maxRequests = Number(envVar[`API_RATE_LIMIT_${target}_MAX_REQUESTS`]);
 
-    if (endpoint == null || consumePoints == null) {
+    if (endpoint == null || maxRequests == null) {
       return;
     }
 
     const config = {
       method,
-      consumePoints,
+      maxRequests,
     };
 
     apiRateLimitConfig[endpoint] = config;
@@ -51,7 +51,7 @@ export const generateApiRateLimitConfig = (): IApiRateLimitConfig => {
   // default setting e.g. healthchack
   apiRateLimitConfig['/_api/v3/healthcheck'] = {
     method: 'GET',
-    consumePoints: 0,
+    maxRequests: 0,
   };
 
   return apiRateLimitConfig;