|
|
@@ -1,16 +1,14 @@
|
|
|
import type { IUserHasId } from '@growi/core';
|
|
|
import type { Handler, Request } from 'express';
|
|
|
import md5 from 'md5';
|
|
|
-import { connection } from 'mongoose';
|
|
|
-import { type IRateLimiterMongoOptions, 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';
|
|
|
|
|
|
const logger = loggerFactory('growi:middleware:api-rate-limit');
|
|
|
|
|
|
@@ -19,15 +17,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 POINTS_THRESHOLD = 100;
|
|
|
-
|
|
|
-const opts: IRateLimiterMongoOptions = {
|
|
|
- storeClient: connection,
|
|
|
- points: POINTS_THRESHOLD, // set default value
|
|
|
- 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;
|
|
|
@@ -37,31 +26,6 @@ const keysWithRegExp = Object.keys(configWithRegExp).map(key => new RegExp(`^${k
|
|
|
const valuesWithRegExp = Object.values(configWithRegExp);
|
|
|
|
|
|
|
|
|
-const _consumePoints = async(
|
|
|
- method: string, key: string | null, customizedConfig?: IApiRateLimitConfig, maxRequestsMultiplier?: number,
|
|
|
-) => {
|
|
|
- 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;
|
|
|
- }
|
|
|
-
|
|
|
- // because the maximum request is reduced by 1 if it is divisible by
|
|
|
- // https://github.com/weseek/growi/pull/6225
|
|
|
- const consumePoints = (POINTS_THRESHOLD + 0.0001) / maxRequests;
|
|
|
- await rateLimiter.consume(key, consumePoints);
|
|
|
-};
|
|
|
-
|
|
|
/**
|
|
|
* consume per user per endpoint
|
|
|
* @param method
|
|
|
@@ -69,8 +33,10 @@ const _consumePoints = async(
|
|
|
* @param customizedConfig
|
|
|
* @returns
|
|
|
*/
|
|
|
-const consumePointsByUser = async(method: string, key: string | null, customizedConfig?: IApiRateLimitConfig) => {
|
|
|
- return _consumePoints(method, key, customizedConfig);
|
|
|
+const consumePointsByUser = async(
|
|
|
+ method: string, key: string | null, customizedConfig?: IApiRateLimitConfig,
|
|
|
+): Promise<RateLimiterRes | undefined> => {
|
|
|
+ return consumePoints(method, key, customizedConfig);
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
@@ -80,9 +46,11 @@ const consumePointsByUser = async(method: string, key: string | null, customized
|
|
|
* @param customizedConfig
|
|
|
* @returns
|
|
|
*/
|
|
|
-const consumePointsByIp = async(method: string, key: string | null, customizedConfig?: IApiRateLimitConfig) => {
|
|
|
+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(method, key, customizedConfig, maxRequestsMultiplier);
|
|
|
};
|
|
|
|
|
|
|