Merge pull request #28708 from drodil/backend_rate_limit
Backend rate limit
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
---
|
||||
'@backstage/backend-defaults': patch
|
||||
---
|
||||
|
||||
Added new rate limit middleware to allow rate limiting requests to the backend
|
||||
|
||||
If you are using the `configure` callback of the root HTTP router service and do NOT call `applyDefaults()` inside it, please see [the relevant changes](https://github.com/backstage/backstage/pull/28708/files#diff-86ad1b6a694dd250823aee39d410428dd837c9d9a04ca8c33bd1081fbe3f22af) that were made, to see if you want to apply them as well to your custom configuration.
|
||||
Rate limiting can be turned on by adding the following configuration to `app-config.yaml`:
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
rateLimit:
|
||||
window: 6s
|
||||
incomingRequestLimit: 100
|
||||
```
|
||||
|
||||
Plugin specific rate limiting can be configured by adding the following configuration to `app-config.yaml`:
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
rateLimit:
|
||||
global: false # This will disable the global rate limiting
|
||||
plugin:
|
||||
catalog:
|
||||
window: 6s
|
||||
incomingRequestLimit: 100
|
||||
```
|
||||
@@ -36,6 +36,12 @@ backend:
|
||||
# keys:
|
||||
# - secret: ${BACKEND_SECRET}
|
||||
|
||||
# Used for testing rate limiting locally
|
||||
# rateLimit:
|
||||
# windowMs: 1m
|
||||
# incomingRequestLimit: 1
|
||||
# ipAllowList: []
|
||||
|
||||
auth:
|
||||
# TODO: once plugins have been migrated we can remove this, but right now it
|
||||
# is require for the backend-next to work in this repo
|
||||
|
||||
@@ -68,6 +68,60 @@ For those routes you will also have to specify `allowLimitedAccess: true` when
|
||||
using the [`auth`](./auth.md) and [`httpAuth`](./http-auth.md) services to
|
||||
access the incoming credentials.
|
||||
|
||||
## Rate limiting
|
||||
|
||||
Rate limiting allows you to limit the amount of requests users can send to you backend.
|
||||
This is useful for blocking various network attacks, such as DDOS.
|
||||
|
||||
To enable rate limiting, add the following to your config:
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
rateLimit: true
|
||||
```
|
||||
|
||||
You can additionally configure the rate limiting parameters, also by plugin:
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
rateLimit:
|
||||
global: true # Enables or disables rate limit for all plugins
|
||||
window: 6s # Time window for rate limiting for single client
|
||||
incomingRequestLimit: 100 # Number of requests to accept from one client during time window
|
||||
ipAllowList: ['127.0.0.1'] # IPs to bypass rate limiting
|
||||
skipSuccesfulRequests: false # Rate limit successful requests
|
||||
skipFailedRequests: false # Rate limit failed requests
|
||||
plugin:
|
||||
# Plugin specific rate limiting
|
||||
catalog:
|
||||
window: 3s
|
||||
incomingRequestLimit: 50
|
||||
```
|
||||
|
||||
By default, the rate limiting is per instance and the request counts are stored into memory.
|
||||
If you want to share this information across all your backstage instances, you have to configure
|
||||
the rate limiting store:
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
rateLimit:
|
||||
global: true
|
||||
store:
|
||||
type: redis
|
||||
connection: redis://127.0.0.1:16379
|
||||
```
|
||||
|
||||
If your instance is working behind a proxy, you have to configure the backend to trust the proxy
|
||||
for the rate limiting being able to distinguish clients.
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
trustProxy: true
|
||||
```
|
||||
|
||||
For more information about the trust proxy configuration and available options,
|
||||
please refer to [express documentation](https://expressjs.com/en/guide/behind-proxies.html).
|
||||
|
||||
## Configuring the service
|
||||
|
||||
For more advanced customization, there are several APIs from the `@backstage/backend-defaults/httpRouter` package that allow you to customize the implementation of the config service. The default implementation uses all of the middleware exported from `@backstage/backend-defaults/httpRouter`, including `createLifecycleMiddleware`, `createAuthIntegrationRouter`, `createCredentialsBarrier` and `createCookieAuthRefreshMiddleware`. You can use these to create your own `httpRouter` service implementation, for example - here's how you would add a custom health check route to all plugins:
|
||||
@@ -78,6 +132,7 @@ import {
|
||||
createCookieAuthRefreshMiddleware,
|
||||
createCredentialsBarrier,
|
||||
createAuthIntegrationRouter,
|
||||
createRateLimitMiddleware,
|
||||
} from '@backstage/backend-defaults/httpRouter';
|
||||
import { createServiceFactory } from '@backstage/backend-plugin-api';
|
||||
|
||||
@@ -105,6 +160,11 @@ backend.add(
|
||||
}) {
|
||||
const router = PromiseRouter();
|
||||
|
||||
// Optional rate limiting middleware
|
||||
router.use(
|
||||
createRateLimitMiddleware({ pluginId: plugin.getId(), config }),
|
||||
);
|
||||
|
||||
rootHttpRouter.use(`/api/${plugin.getId()}`, router);
|
||||
|
||||
const credentialsBarrier = createCredentialsBarrier({
|
||||
|
||||
@@ -40,6 +40,10 @@ createBackendPlugin({
|
||||
});
|
||||
```
|
||||
|
||||
## Rate limiting
|
||||
|
||||
Please refer to the [HTTP Router documentation](./http-router.md#rate-limiting).
|
||||
|
||||
## Configuring the service
|
||||
|
||||
### Via `app-config.yaml`
|
||||
@@ -121,6 +125,11 @@ backend.add(
|
||||
app.use(middleware.cors());
|
||||
app.use(middleware.compression());
|
||||
|
||||
// Optional rate limiting middleware
|
||||
app.use(middleware.rateLimit());
|
||||
// If you are using rate limiting behind a proxy, you should set the `trust proxy` setting to true
|
||||
app.set('trust proxy', true);
|
||||
|
||||
app.use(healthRouter);
|
||||
|
||||
// you can add you your own middleware in here
|
||||
|
||||
+86
@@ -790,6 +790,92 @@ export interface Config {
|
||||
headers?: { [name: string]: string };
|
||||
};
|
||||
|
||||
/**
|
||||
* Rate limiting options. Defining this as `true` will enable rate limiting with default values.
|
||||
*/
|
||||
rateLimit?:
|
||||
| true
|
||||
| {
|
||||
store?:
|
||||
| {
|
||||
type: 'redis';
|
||||
connection: string;
|
||||
}
|
||||
| {
|
||||
type: 'memory';
|
||||
};
|
||||
/**
|
||||
* Enable/disable global rate limiting. If this is disabled, plugin specific rate limiting must be
|
||||
* used.
|
||||
*/
|
||||
global?: boolean;
|
||||
/**
|
||||
* Time frame in milliseconds or as human duration for which requests are checked/remembered.
|
||||
* Defaults to one minute.
|
||||
*/
|
||||
window?: string | HumanDuration;
|
||||
/**
|
||||
* The maximum number of connections to allow during the `window` before rate limiting the client.
|
||||
* Defaults to 5.
|
||||
*/
|
||||
incomingRequestLimit?: number;
|
||||
/**
|
||||
* Whether to pass requests in case of store failure.
|
||||
* Defaults to false.
|
||||
*/
|
||||
passOnStoreError?: boolean;
|
||||
/**
|
||||
* List of allowed IP addresses that are not rate limited.
|
||||
* Defaults to [127.0.0.1, 0:0:0:0:0:0:0:1, ::1].
|
||||
*/
|
||||
ipAllowList?: string[];
|
||||
/**
|
||||
* Skip rate limiting for requests that have been successful.
|
||||
* Defaults to false.
|
||||
*/
|
||||
skipSuccessfulRequests?: boolean;
|
||||
/**
|
||||
* Skip rate limiting for requests that have failed.
|
||||
* Defaults to false.
|
||||
*/
|
||||
skipFailedRequests?: boolean;
|
||||
/** Plugin specific rate limiting configuration */
|
||||
plugin?: {
|
||||
[pluginId: string]: {
|
||||
/**
|
||||
* Time frame in milliseconds or as human duration for which requests are checked/remembered.
|
||||
* Defaults to one minute.
|
||||
*/
|
||||
window?: string | HumanDuration;
|
||||
/**
|
||||
* The maximum number of connections to allow during the `window` before rate limiting the client.
|
||||
* Defaults to 5.
|
||||
*/
|
||||
incomingRequestLimit?: number;
|
||||
/**
|
||||
* Whether to pass requests in case of store failure.
|
||||
* Defaults to false.
|
||||
*/
|
||||
passOnStoreError?: boolean;
|
||||
/**
|
||||
* List of allowed IP addresses that are not rate limited.
|
||||
* Defaults to [127.0.0.1, 0:0:0:0:0:0:0:1, ::1].
|
||||
*/
|
||||
ipAllowList?: string[];
|
||||
/**
|
||||
* Skip rate limiting for requests that have been successful.
|
||||
* Defaults to false.
|
||||
*/
|
||||
skipSuccessfulRequests?: boolean;
|
||||
/**
|
||||
* Skip rate limiting for requests that have failed.
|
||||
* Defaults to false.
|
||||
*/
|
||||
skipFailedRequests?: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Configuration related to URL reading, used for example for reading catalog info
|
||||
* files, scaffolder templates, and techdocs content.
|
||||
|
||||
@@ -168,6 +168,7 @@
|
||||
"cron": "^3.0.0",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"git-url-parse": "^15.0.0",
|
||||
"helmet": "^6.0.0",
|
||||
@@ -187,6 +188,7 @@
|
||||
"pg": "^8.11.3",
|
||||
"pg-connection-string": "^2.3.0",
|
||||
"pg-format": "^1.0.4",
|
||||
"rate-limit-redis": "^4.2.0",
|
||||
"raw-body": "^2.4.1",
|
||||
"selfsigned": "^2.0.0",
|
||||
"tar": "^6.1.12",
|
||||
|
||||
@@ -93,6 +93,7 @@ export class MiddlewareFactory {
|
||||
helmet(): RequestHandler;
|
||||
logging(): RequestHandler;
|
||||
notFound(): RequestHandler;
|
||||
rateLimit(): RequestHandler;
|
||||
}
|
||||
|
||||
// @public
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
import { RateLimitStoreFactory } from '../../../lib/RateLimitStoreFactory.ts';
|
||||
import { Config } from '@backstage/config';
|
||||
import { rateLimitMiddleware } from '../../../lib/rateLimitMiddleware.ts';
|
||||
|
||||
export const createRateLimitMiddleware = (options: {
|
||||
pluginId: string;
|
||||
config: Config;
|
||||
}) => {
|
||||
const { pluginId, config } = options;
|
||||
const configKey = `backend.rateLimit.plugin.${pluginId}`;
|
||||
const enabled = config.has(configKey);
|
||||
if (!enabled) {
|
||||
return (_req: Request, _res: Response, next: NextFunction) => {
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
const rateLimitOptions = config.getConfig(configKey);
|
||||
|
||||
return rateLimitMiddleware({
|
||||
store: RateLimitStoreFactory.create({ config, prefix: pluginId }),
|
||||
config: rateLimitOptions,
|
||||
});
|
||||
};
|
||||
@@ -22,12 +22,13 @@ import {
|
||||
HttpRouterServiceAuthPolicy,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
createLifecycleMiddleware,
|
||||
createAuthIntegrationRouter,
|
||||
createCookieAuthRefreshMiddleware,
|
||||
createCredentialsBarrier,
|
||||
createAuthIntegrationRouter,
|
||||
createLifecycleMiddleware,
|
||||
} from './http';
|
||||
import { MiddlewareFactory } from '../rootHttpRouter';
|
||||
import { createRateLimitMiddleware } from './http/createRateLimitMiddleware.ts';
|
||||
|
||||
/**
|
||||
* HTTP route registration for plugins.
|
||||
@@ -61,6 +62,8 @@ export const httpRouterServiceFactory = createServiceFactory({
|
||||
}) {
|
||||
const router = PromiseRouter();
|
||||
|
||||
router.use(createRateLimitMiddleware({ pluginId: plugin.getId(), config }));
|
||||
|
||||
rootHttpRouter.use(`/api/${plugin.getId()}`, router);
|
||||
|
||||
const credentialsBarrier = createCredentialsBarrier({
|
||||
|
||||
@@ -15,15 +15,15 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
RootConfigService,
|
||||
LoggerService,
|
||||
RootConfigService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
Request,
|
||||
Response,
|
||||
ErrorRequestHandler,
|
||||
NextFunction,
|
||||
Request,
|
||||
RequestHandler,
|
||||
Response,
|
||||
} from 'express';
|
||||
import cors from 'cors';
|
||||
import helmet from 'helmet';
|
||||
@@ -37,12 +37,14 @@ import {
|
||||
InputError,
|
||||
NotAllowedError,
|
||||
NotFoundError,
|
||||
NotImplementedError,
|
||||
NotModifiedError,
|
||||
ServiceUnavailableError,
|
||||
serializeError,
|
||||
ServiceUnavailableError,
|
||||
} from '@backstage/errors';
|
||||
import { NotImplementedError } from '@backstage/errors';
|
||||
import { applyInternalErrorFilter } from './applyInternalErrorFilter';
|
||||
import { RateLimitStoreFactory } from '../../../lib/RateLimitStoreFactory.ts';
|
||||
import { rateLimitMiddleware } from '../../../lib/rateLimitMiddleware.ts';
|
||||
|
||||
type LogMeta = {
|
||||
date: string;
|
||||
@@ -227,6 +229,47 @@ export class MiddlewareFactory {
|
||||
return cors(readCorsOptions(this.#config.getOptionalConfig('backend')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a middleware that implements rate limiting.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Rate limiting is a common technique to prevent abuse of APIs. This middleware is
|
||||
* configured using the config key `backend.rateLimit`.
|
||||
*
|
||||
* @returns An Express request handler
|
||||
*/
|
||||
rateLimit(): RequestHandler {
|
||||
const enabled = this.#config.has('backend.rateLimit');
|
||||
if (!enabled) {
|
||||
return (_req: Request, _res: Response, next: NextFunction) => {
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
const useDefaults = this.#config.getOptional('backend.rateLimit') === true;
|
||||
const rateLimitOptions = useDefaults
|
||||
? undefined
|
||||
: this.#config.getOptionalConfig('backend.rateLimit');
|
||||
|
||||
// Global rate limiting disabled
|
||||
if (
|
||||
rateLimitOptions &&
|
||||
rateLimitOptions.getOptionalBoolean('global') === false
|
||||
) {
|
||||
return (_req: Request, _res: Response, next: NextFunction) => {
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
return rateLimitMiddleware({
|
||||
store: useDefaults
|
||||
? undefined
|
||||
: RateLimitStoreFactory.create({ config: this.#config }),
|
||||
config: rateLimitOptions,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Express middleware to handle errors during request processing.
|
||||
*
|
||||
|
||||
+3
-2
@@ -15,13 +15,13 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
RootConfigService,
|
||||
coreServices,
|
||||
createServiceFactory,
|
||||
LifecycleService,
|
||||
LoggerService,
|
||||
RootConfigService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import express, { RequestHandler, Express } from 'express';
|
||||
import express, { Express, RequestHandler } from 'express';
|
||||
import type { Server } from 'node:http';
|
||||
import {
|
||||
createHttpServer,
|
||||
@@ -121,6 +121,7 @@ const rootHttpRouterServiceFactoryWithOptions = (
|
||||
app.use(middleware.cors());
|
||||
app.use(middleware.compression());
|
||||
app.use(middleware.logging());
|
||||
app.use(middleware.rateLimit());
|
||||
app.use(healthRouter);
|
||||
app.use(routes);
|
||||
app.use(middleware.notFound());
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { mockServices } from '@backstage/backend-test-utils';
|
||||
import { RateLimitStoreFactory } from './RateLimitStoreFactory.ts';
|
||||
import { RedisStore } from 'rate-limit-redis';
|
||||
|
||||
jest.mock('@keyv/redis', () => {
|
||||
const Actual = jest.requireActual('@keyv/redis');
|
||||
return {
|
||||
...Actual,
|
||||
__esModule: true,
|
||||
default: jest.fn(() => {
|
||||
return {
|
||||
getClient: jest.fn(() => ({
|
||||
sendCommand: jest.fn().mockReturnValue('mock'),
|
||||
})),
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('CacheRateLimitStoreFactory', () => {
|
||||
afterEach(jest.clearAllMocks);
|
||||
|
||||
it('should return undefined store with auto configuration if redis is not available', () => {
|
||||
const config = mockServices.rootConfig({
|
||||
data: {
|
||||
backend: {
|
||||
rateLimit: {
|
||||
store: undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const store = RateLimitStoreFactory.create({ config });
|
||||
expect(store).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return redis store if configured explicitly', async () => {
|
||||
const config = mockServices.rootConfig({
|
||||
data: {
|
||||
backend: {
|
||||
rateLimit: {
|
||||
store: {
|
||||
type: 'redis',
|
||||
connection: 'redis://localhost:6379',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const store = RateLimitStoreFactory.create({ config });
|
||||
expect(store).toBeInstanceOf(RedisStore);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Config } from '@backstage/config';
|
||||
import type { Store } from 'express-rate-limit';
|
||||
import { RedisStore } from 'rate-limit-redis';
|
||||
|
||||
/**
|
||||
* Creates a store for `express-rate-limit` based on the configuration.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export class RateLimitStoreFactory {
|
||||
static create(options: {
|
||||
config: Config;
|
||||
prefix?: string;
|
||||
}): Store | undefined {
|
||||
const { config, prefix } = options;
|
||||
const store = config.getOptionalConfig('backend.rateLimit.store');
|
||||
if (!store) {
|
||||
return undefined;
|
||||
}
|
||||
const type = store.getString('type');
|
||||
switch (type) {
|
||||
case 'redis':
|
||||
return this.redis({ store, prefix });
|
||||
case 'memory':
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private static redis(options: { store: Config; prefix?: string }): Store {
|
||||
const { store, prefix } = options;
|
||||
const connectionString = store.getString('connection');
|
||||
const KeyvRedis = require('@keyv/redis').default;
|
||||
const keyv = new KeyvRedis(connectionString);
|
||||
return new RedisStore({
|
||||
prefix,
|
||||
sendCommand: async (...args: string[]) => {
|
||||
const client = await keyv.getClient();
|
||||
return client.sendCommand(args);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { RequestHandler } from 'express';
|
||||
import { rateLimit, Store } from 'express-rate-limit';
|
||||
import { Config, readDurationFromConfig } from '@backstage/config';
|
||||
import { durationToMilliseconds } from '@backstage/types';
|
||||
|
||||
export const rateLimitMiddleware = (options: {
|
||||
store?: Store;
|
||||
config?: Config;
|
||||
}): RequestHandler => {
|
||||
const { store, config } = options;
|
||||
let windowMs: number = 60000;
|
||||
if (config && config.has('window')) {
|
||||
const windowDuration = readDurationFromConfig(config, {
|
||||
key: 'window',
|
||||
});
|
||||
windowMs = durationToMilliseconds(windowDuration);
|
||||
}
|
||||
const limit = config?.getOptionalNumber('incomingRequestLimit');
|
||||
const ipAllowList = config?.getOptionalStringArray('ipAllowList') ?? [
|
||||
'127.0.0.1',
|
||||
'0:0:0:0:0:0:0:1',
|
||||
'::1',
|
||||
];
|
||||
const skipSuccessfulRequests = config?.getOptionalBoolean(
|
||||
'skipSuccessfulRequests',
|
||||
);
|
||||
const skipFailedRequests = config?.getOptionalBoolean('skipFailedRequests');
|
||||
const passOnStoreError = config?.getOptionalBoolean('passOnStoreError');
|
||||
|
||||
return rateLimit({
|
||||
windowMs,
|
||||
limit,
|
||||
skipSuccessfulRequests,
|
||||
message: {
|
||||
error: {
|
||||
name: 'Error',
|
||||
message: `Too many requests, please try again later`,
|
||||
},
|
||||
response: {
|
||||
statusCode: 429,
|
||||
},
|
||||
},
|
||||
statusCode: 429,
|
||||
skipFailedRequests,
|
||||
passOnStoreError: passOnStoreError,
|
||||
keyGenerator(req, _res): string {
|
||||
if (!req.ip) {
|
||||
return req.socket.remoteAddress!;
|
||||
}
|
||||
return req.ip;
|
||||
},
|
||||
skip: (req, _res) => {
|
||||
return (
|
||||
Boolean(req.ip && ipAllowList.includes(req.ip)) ||
|
||||
Boolean(
|
||||
req.socket.remoteAddress &&
|
||||
ipAllowList.includes(req.socket.remoteAddress),
|
||||
)
|
||||
);
|
||||
},
|
||||
validate: {
|
||||
trustProxy: false,
|
||||
},
|
||||
store,
|
||||
});
|
||||
};
|
||||
@@ -3602,6 +3602,7 @@ __metadata:
|
||||
cron: "npm:^3.0.0"
|
||||
express: "npm:^4.17.1"
|
||||
express-promise-router: "npm:^4.1.0"
|
||||
express-rate-limit: "npm:^7.5.0"
|
||||
fs-extra: "npm:^11.2.0"
|
||||
git-url-parse: "npm:^15.0.0"
|
||||
helmet: "npm:^6.0.0"
|
||||
@@ -3624,6 +3625,7 @@ __metadata:
|
||||
pg: "npm:^8.11.3"
|
||||
pg-connection-string: "npm:^2.3.0"
|
||||
pg-format: "npm:^1.0.4"
|
||||
rate-limit-redis: "npm:^4.2.0"
|
||||
raw-body: "npm:^2.4.1"
|
||||
selfsigned: "npm:^2.0.0"
|
||||
supertest: "npm:^7.0.0"
|
||||
@@ -31437,6 +31439,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"express-rate-limit@npm:^7.5.0":
|
||||
version: 7.5.0
|
||||
resolution: "express-rate-limit@npm:7.5.0"
|
||||
peerDependencies:
|
||||
express: ^4.11 || 5 || ^5.0.0-beta.1
|
||||
checksum: 10/eff34c83bf586789933a332a339b66649e2cca95c8e977d193aa8bead577d3182ac9f0e9c26f39389287539b8038890ff023f910b54ebb506a26a2ce135b92ca
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"express-session@npm:^1.17.1, express-session@npm:^1.17.3":
|
||||
version: 1.18.1
|
||||
resolution: "express-session@npm:1.18.1"
|
||||
@@ -43415,6 +43426,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rate-limit-redis@npm:^4.2.0":
|
||||
version: 4.2.0
|
||||
resolution: "rate-limit-redis@npm:4.2.0"
|
||||
peerDependencies:
|
||||
express-rate-limit: ">= 6"
|
||||
checksum: 10/22adc67918ca906f613b45f9dcfd039f543d363921979d21ba56be5f3288c6e9973c9e4bb4ec59810fc6b3abb20defd572c102f607a8c3b4d273d5e09b63839f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rate-limiter-flexible@npm:^4.0.1":
|
||||
version: 4.0.1
|
||||
resolution: "rate-limiter-flexible@npm:4.0.1"
|
||||
|
||||
Reference in New Issue
Block a user