feat(backend): allow rate limiting requests to the backend
uses redis for storing the data if it has been configured; otherwise falls back to memory configuration allows controlling almost all possible configuration available in the `express-rate-limit` library. Signed-off-by: Heikki Hellgren <heikki.hellgren@op.fi>
This commit is contained in:
committed by
Hellgren Heikki
parent
58cb0452a9
commit
1220cf84d0
@@ -0,0 +1,15 @@
|
||||
---
|
||||
'@backstage/backend-defaults': patch
|
||||
---
|
||||
|
||||
Added new rate limit middleware to allow rate limiting requests to the backend
|
||||
|
||||
Rate limiting can be turned on by adding the following configuration to `app-config.yaml`:
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
rateLimit:
|
||||
enabled: true
|
||||
windowMs: 60000
|
||||
limit: 100
|
||||
```
|
||||
+67
-1
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { HumanDuration } from '@backstage/types';
|
||||
import { HumanDuration, JsonValue } from '@backstage/types';
|
||||
|
||||
export interface Config {
|
||||
app: {
|
||||
@@ -790,6 +790,72 @@ export interface Config {
|
||||
headers?: { [name: string]: string };
|
||||
};
|
||||
|
||||
/**
|
||||
* Rate limiting options
|
||||
*/
|
||||
rateLimit?: {
|
||||
/**
|
||||
* Store to use for rate limiting. If not defined, the store will be automatically
|
||||
* decided based on `backend.cache.store` value. If
|
||||
* redis is not available, the store will be memory.
|
||||
*/
|
||||
store?: 'memory' | 'redis';
|
||||
/**
|
||||
* Rate limiting enabled. Defaults to false.
|
||||
*/
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* Time frame in milliseconds or as human duration for which requests are checked/remembered.
|
||||
* Defaults to 6000ms.
|
||||
*/
|
||||
window?: number | HumanDuration;
|
||||
/**
|
||||
* The maximum number of connections to allow during the `window` before rate limiting the client.
|
||||
* Defaults to 5.
|
||||
*/
|
||||
limit?: number;
|
||||
/**
|
||||
* The response body to send back when a client is rate limited.
|
||||
* Defaults to 'Too many requests, please try again later.'.
|
||||
*/
|
||||
message?: JsonValue;
|
||||
/**
|
||||
* The HTTP status code to send back when a client is rate limited.
|
||||
* Defaults to 429.
|
||||
*/
|
||||
statusCode?: number;
|
||||
/**
|
||||
* Whether to send the legacy rate limit headers for the limit.
|
||||
* Defaults to true.
|
||||
*/
|
||||
legacyHeaders?: boolean;
|
||||
/**
|
||||
* Whether to enable support for headers conforming the RateLimit header fields for HTTP
|
||||
* standardization. Defaults to undefined.
|
||||
*/
|
||||
standardHeaders?: 'draft-6' | 'draft-7';
|
||||
/**
|
||||
* 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].
|
||||
*/
|
||||
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.4.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",
|
||||
|
||||
@@ -20,6 +20,7 @@ import { RootHttpRouterService } from '@backstage/backend-plugin-api';
|
||||
import { Router } from 'express';
|
||||
import type { Server } from 'node:http';
|
||||
import { ServiceFactory } from '@backstage/backend-plugin-api';
|
||||
import type { Store } from 'express-rate-limit';
|
||||
|
||||
// @public (undocumented)
|
||||
export function createHealthRouter(options: {
|
||||
@@ -93,6 +94,7 @@ export class MiddlewareFactory {
|
||||
helmet(): RequestHandler;
|
||||
logging(): RequestHandler;
|
||||
notFound(): RequestHandler;
|
||||
rateLimit(): RequestHandler;
|
||||
}
|
||||
|
||||
// @public
|
||||
@@ -109,6 +111,15 @@ export interface MiddlewareFactoryOptions {
|
||||
logger: LoggerService;
|
||||
}
|
||||
|
||||
// @public
|
||||
export class RateLimitStoreFactory {
|
||||
constructor(config: Config);
|
||||
// (undocumented)
|
||||
create(): Store | undefined;
|
||||
// (undocumented)
|
||||
redis(): Store;
|
||||
}
|
||||
|
||||
// @public
|
||||
export function readCorsOptions(config?: Config): CorsOptions;
|
||||
|
||||
|
||||
@@ -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,19 @@ import {
|
||||
InputError,
|
||||
NotAllowedError,
|
||||
NotFoundError,
|
||||
NotImplementedError,
|
||||
NotModifiedError,
|
||||
ServiceUnavailableError,
|
||||
serializeError,
|
||||
ServiceUnavailableError,
|
||||
} from '@backstage/errors';
|
||||
import { NotImplementedError } from '@backstage/errors';
|
||||
import { applyInternalErrorFilter } from './applyInternalErrorFilter';
|
||||
import { DraftHeadersVersion, rateLimit } from 'express-rate-limit';
|
||||
import {
|
||||
durationToMilliseconds,
|
||||
HumanDuration,
|
||||
JsonValue,
|
||||
} from '@backstage/types';
|
||||
import { RateLimitStoreFactory } from './RateLimitStoreFactory';
|
||||
|
||||
type LogMeta = {
|
||||
date: string;
|
||||
@@ -227,6 +234,75 @@ 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 rateLimitOptions =
|
||||
this.#config.getOptionalConfig('backend.rateLimit');
|
||||
const enabled = rateLimitOptions?.getOptionalBoolean('enabled') ?? false;
|
||||
if (!rateLimitOptions || !enabled) {
|
||||
return (_req: Request, _res: Response, next: NextFunction) => {
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
const window = rateLimitOptions.getOptional<number | HumanDuration>(
|
||||
'window',
|
||||
);
|
||||
let windowMs: number | undefined;
|
||||
if (window !== undefined) {
|
||||
if (typeof window === 'number') {
|
||||
windowMs = window;
|
||||
} else if (typeof window === 'object' && !Array.isArray(window)) {
|
||||
windowMs = durationToMilliseconds(window);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Invalid configuration backend.rateLimit.window: ${window}, expected milliseconds number or HumanDuration object`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const ipAllowList = rateLimitOptions.getOptionalStringArray(
|
||||
'ipAllowList',
|
||||
) ?? ['127.0.0.1'];
|
||||
|
||||
return rateLimit({
|
||||
windowMs,
|
||||
limit: rateLimitOptions.getOptionalNumber('limit'),
|
||||
message: rateLimitOptions.getOptional<string | JsonValue>('message'),
|
||||
statusCode: rateLimitOptions.getOptionalNumber('statusCode'),
|
||||
skipSuccessfulRequests: rateLimitOptions.getOptionalBoolean(
|
||||
'skipSuccessfulRequests',
|
||||
),
|
||||
skipFailedRequests:
|
||||
rateLimitOptions.getOptionalBoolean('skipFailedRequests'),
|
||||
legacyHeaders: rateLimitOptions.getOptionalBoolean('legacyHeaders'),
|
||||
standardHeaders: rateLimitOptions.getOptionalString(
|
||||
'standardHeaders',
|
||||
) as DraftHeadersVersion,
|
||||
passOnStoreError: rateLimitOptions.getOptionalBoolean('passOnStoreError'),
|
||||
keyGenerator(req, _res): string {
|
||||
if (!req.ip) {
|
||||
return req.socket.remoteAddress!;
|
||||
}
|
||||
|
||||
return req.ip.replace(/:\d+[^:]*$/, '');
|
||||
},
|
||||
skip: (req, _res) => {
|
||||
return Boolean(req.ip && ipAllowList.includes(req.ip));
|
||||
},
|
||||
store: new RateLimitStoreFactory(this.#config).create(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Express middleware to handle errors during request processing.
|
||||
*
|
||||
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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';
|
||||
import { RedisStore } from 'rate-limit-redis';
|
||||
import KeyvRedis from '@keyv/redis';
|
||||
|
||||
jest.mock('@keyv/redis');
|
||||
(KeyvRedis as jest.Mocked<any>).mockImplementation(() => {
|
||||
return {
|
||||
redis: {
|
||||
call: jest.fn().mockResolvedValue('OK'),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe('CacheRateLimitStoreFactory', () => {
|
||||
it('should return redis store with auto configuration', () => {
|
||||
const config = mockServices.rootConfig({
|
||||
data: {
|
||||
backend: {
|
||||
cache: {
|
||||
store: 'redis',
|
||||
},
|
||||
rateLimit: {
|
||||
store: undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const factory = new RateLimitStoreFactory(config);
|
||||
const store = factory.create();
|
||||
expect(store).toBeInstanceOf(RedisStore);
|
||||
});
|
||||
|
||||
it('should return undefined store with auto configuration if redis is not available', () => {
|
||||
const config = mockServices.rootConfig({
|
||||
data: {
|
||||
backend: {
|
||||
cache: {
|
||||
store: 'memory',
|
||||
},
|
||||
database: {
|
||||
client: 'sqlite3',
|
||||
},
|
||||
rateLimit: {
|
||||
store: undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const factory = new RateLimitStoreFactory(config);
|
||||
const store = factory.create();
|
||||
expect(store).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return redis store if configured explicitly', () => {
|
||||
const config = mockServices.rootConfig({
|
||||
data: {
|
||||
backend: {
|
||||
rateLimit: {
|
||||
store: 'redis',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const factory = new RateLimitStoreFactory(config);
|
||||
const store = factory.create();
|
||||
expect(store).toBeInstanceOf(RedisStore);
|
||||
});
|
||||
});
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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 KeyvRedis from '@keyv/redis';
|
||||
import { RedisStore } from 'rate-limit-redis';
|
||||
|
||||
/**
|
||||
* Creates a store for `express-rate-limit` based on the configuration.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class RateLimitStoreFactory {
|
||||
constructor(private readonly config: Config) {}
|
||||
|
||||
create(): Store | undefined {
|
||||
const storeType = this.config.getOptionalString('backend.rateLimit.store');
|
||||
if (!storeType) {
|
||||
return this.auto();
|
||||
}
|
||||
switch (storeType) {
|
||||
case 'redis':
|
||||
return this.redis();
|
||||
default:
|
||||
throw new Error(
|
||||
`Invalid 'backend.rateLimit.store' provided: ${storeType}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private auto(): Store | undefined {
|
||||
const cacheStore =
|
||||
this.config.getOptionalString('backend.cache.store') || 'memory';
|
||||
// Use redis as primary if available
|
||||
if (cacheStore === 'redis') {
|
||||
return this.redis();
|
||||
}
|
||||
|
||||
// Fallback to undefined (memory)
|
||||
return undefined;
|
||||
}
|
||||
|
||||
redis(): Store {
|
||||
const connectionString =
|
||||
this.config.getOptionalString('backend.cache.connection') || '';
|
||||
const useRedisSets =
|
||||
this.config.getOptionalBoolean('backend.cache.useRedisSets') ?? true;
|
||||
const keyv = new KeyvRedis(connectionString, {
|
||||
useRedisSets,
|
||||
});
|
||||
return new RedisStore({
|
||||
// Keyv uses ioredis under the hood
|
||||
sendCommand: (...args: string[]) => keyv.redis.call(...args),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -28,3 +28,4 @@ export type {
|
||||
HttpServerCertificateOptions,
|
||||
HttpServerOptions,
|
||||
} from './types';
|
||||
export { RateLimitStoreFactory } from './RateLimitStoreFactory';
|
||||
|
||||
+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,
|
||||
@@ -117,6 +117,7 @@ const rootHttpRouterServiceFactoryWithOptions = (
|
||||
if (trustProxy !== undefined) {
|
||||
app.set('trust proxy', trustProxy);
|
||||
}
|
||||
app.use(middleware.rateLimit());
|
||||
app.use(middleware.helmet());
|
||||
app.use(middleware.cors());
|
||||
app.use(middleware.compression());
|
||||
|
||||
@@ -3335,7 +3335,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.26.10, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2":
|
||||
"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2":
|
||||
version: 7.26.7
|
||||
resolution: "@babel/runtime@npm:7.26.7"
|
||||
dependencies:
|
||||
regenerator-runtime: "npm:^0.14.0"
|
||||
checksum: 10/c7a661a6836b332d9d2e047cba77ba1862c1e4f78cec7146db45808182ef7636d8a7170be9797e5d8fd513180bffb9fa16f6ca1c69341891efec56113cf22bfc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/runtime@npm:^7.26.10":
|
||||
version: 7.27.0
|
||||
resolution: "@babel/runtime@npm:7.27.0"
|
||||
dependencies:
|
||||
@@ -3344,7 +3353,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/template@npm:^7.22.5, @babel/template@npm:^7.24.7, @babel/template@npm:^7.25.9, @babel/template@npm:^7.27.0, @babel/template@npm:^7.3.3":
|
||||
"@babel/template@npm:^7.22.5, @babel/template@npm:^7.24.7, @babel/template@npm:^7.25.9, @babel/template@npm:^7.3.3":
|
||||
version: 7.25.9
|
||||
resolution: "@babel/template@npm:7.25.9"
|
||||
dependencies:
|
||||
"@babel/code-frame": "npm:^7.25.9"
|
||||
"@babel/parser": "npm:^7.25.9"
|
||||
"@babel/types": "npm:^7.25.9"
|
||||
checksum: 10/e861180881507210150c1335ad94aff80fd9e9be6202e1efa752059c93224e2d5310186ddcdd4c0f0b0fc658ce48cb47823f15142b5c00c8456dde54f5de80b2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/template@npm:^7.27.0":
|
||||
version: 7.27.0
|
||||
resolution: "@babel/template@npm:7.27.0"
|
||||
dependencies:
|
||||
@@ -3370,7 +3390,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.10, @babel/types@npm:^7.22.5, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.0, @babel/types@npm:^7.27.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4":
|
||||
"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.10, @babel/types@npm:^7.22.5, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4":
|
||||
version: 7.26.0
|
||||
resolution: "@babel/types@npm:7.26.0"
|
||||
dependencies:
|
||||
"@babel/helper-string-parser": "npm:^7.25.9"
|
||||
"@babel/helper-validator-identifier": "npm:^7.25.9"
|
||||
checksum: 10/40780741ecec886ed9edae234b5eb4976968cc70d72b4e5a40d55f83ff2cc457de20f9b0f4fe9d858350e43dab0ea496e7ef62e2b2f08df699481a76df02cd6e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/types@npm:^7.27.0":
|
||||
version: 7.27.0
|
||||
resolution: "@babel/types@npm:7.27.0"
|
||||
dependencies:
|
||||
@@ -3602,6 +3632,7 @@ __metadata:
|
||||
cron: "npm:^3.0.0"
|
||||
express: "npm:^4.17.1"
|
||||
express-promise-router: "npm:^4.1.0"
|
||||
express-rate-limit: "npm:^7.4.0"
|
||||
fs-extra: "npm:^11.2.0"
|
||||
git-url-parse: "npm:^15.0.0"
|
||||
helmet: "npm:^6.0.0"
|
||||
@@ -3624,6 +3655,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"
|
||||
@@ -19193,6 +19225,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-darwin-arm64@npm:1.10.6":
|
||||
version: 1.10.6
|
||||
resolution: "@swc/core-darwin-arm64@npm:1.10.6"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-darwin-arm64@npm:1.11.24":
|
||||
version: 1.11.24
|
||||
resolution: "@swc/core-darwin-arm64@npm:1.11.24"
|
||||
@@ -19200,6 +19239,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-darwin-x64@npm:1.10.6":
|
||||
version: 1.10.6
|
||||
resolution: "@swc/core-darwin-x64@npm:1.10.6"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-darwin-x64@npm:1.11.24":
|
||||
version: 1.11.24
|
||||
resolution: "@swc/core-darwin-x64@npm:1.11.24"
|
||||
@@ -19207,6 +19253,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-arm-gnueabihf@npm:1.10.6":
|
||||
version: 1.10.6
|
||||
resolution: "@swc/core-linux-arm-gnueabihf@npm:1.10.6"
|
||||
conditions: os=linux & cpu=arm
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-arm-gnueabihf@npm:1.11.24":
|
||||
version: 1.11.24
|
||||
resolution: "@swc/core-linux-arm-gnueabihf@npm:1.11.24"
|
||||
@@ -19214,6 +19267,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-arm64-gnu@npm:1.10.6":
|
||||
version: 1.10.6
|
||||
resolution: "@swc/core-linux-arm64-gnu@npm:1.10.6"
|
||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-arm64-gnu@npm:1.11.24":
|
||||
version: 1.11.24
|
||||
resolution: "@swc/core-linux-arm64-gnu@npm:1.11.24"
|
||||
@@ -19221,6 +19281,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-arm64-musl@npm:1.10.6":
|
||||
version: 1.10.6
|
||||
resolution: "@swc/core-linux-arm64-musl@npm:1.10.6"
|
||||
conditions: os=linux & cpu=arm64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-arm64-musl@npm:1.11.24":
|
||||
version: 1.11.24
|
||||
resolution: "@swc/core-linux-arm64-musl@npm:1.11.24"
|
||||
@@ -19228,6 +19295,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-x64-gnu@npm:1.10.6":
|
||||
version: 1.10.6
|
||||
resolution: "@swc/core-linux-x64-gnu@npm:1.10.6"
|
||||
conditions: os=linux & cpu=x64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-x64-gnu@npm:1.11.24":
|
||||
version: 1.11.24
|
||||
resolution: "@swc/core-linux-x64-gnu@npm:1.11.24"
|
||||
@@ -19235,6 +19309,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-x64-musl@npm:1.10.6":
|
||||
version: 1.10.6
|
||||
resolution: "@swc/core-linux-x64-musl@npm:1.10.6"
|
||||
conditions: os=linux & cpu=x64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-x64-musl@npm:1.11.24":
|
||||
version: 1.11.24
|
||||
resolution: "@swc/core-linux-x64-musl@npm:1.11.24"
|
||||
@@ -19242,6 +19323,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-win32-arm64-msvc@npm:1.10.6":
|
||||
version: 1.10.6
|
||||
resolution: "@swc/core-win32-arm64-msvc@npm:1.10.6"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-win32-arm64-msvc@npm:1.11.24":
|
||||
version: 1.11.24
|
||||
resolution: "@swc/core-win32-arm64-msvc@npm:1.11.24"
|
||||
@@ -19249,6 +19337,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-win32-ia32-msvc@npm:1.10.6":
|
||||
version: 1.10.6
|
||||
resolution: "@swc/core-win32-ia32-msvc@npm:1.10.6"
|
||||
conditions: os=win32 & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-win32-ia32-msvc@npm:1.11.24":
|
||||
version: 1.11.24
|
||||
resolution: "@swc/core-win32-ia32-msvc@npm:1.11.24"
|
||||
@@ -19256,6 +19351,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-win32-x64-msvc@npm:1.10.6":
|
||||
version: 1.10.6
|
||||
resolution: "@swc/core-win32-x64-msvc@npm:1.10.6"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-win32-x64-msvc@npm:1.11.24":
|
||||
version: 1.11.24
|
||||
resolution: "@swc/core-win32-x64-msvc@npm:1.11.24"
|
||||
@@ -19263,7 +19365,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core@npm:^1.10.8, @swc/core@npm:^1.3.46":
|
||||
"@swc/core@npm:^1.10.8":
|
||||
version: 1.11.24
|
||||
resolution: "@swc/core@npm:1.11.24"
|
||||
dependencies:
|
||||
@@ -19309,6 +19411,52 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core@npm:^1.3.46":
|
||||
version: 1.10.6
|
||||
resolution: "@swc/core@npm:1.10.6"
|
||||
dependencies:
|
||||
"@swc/core-darwin-arm64": "npm:1.10.6"
|
||||
"@swc/core-darwin-x64": "npm:1.10.6"
|
||||
"@swc/core-linux-arm-gnueabihf": "npm:1.10.6"
|
||||
"@swc/core-linux-arm64-gnu": "npm:1.10.6"
|
||||
"@swc/core-linux-arm64-musl": "npm:1.10.6"
|
||||
"@swc/core-linux-x64-gnu": "npm:1.10.6"
|
||||
"@swc/core-linux-x64-musl": "npm:1.10.6"
|
||||
"@swc/core-win32-arm64-msvc": "npm:1.10.6"
|
||||
"@swc/core-win32-ia32-msvc": "npm:1.10.6"
|
||||
"@swc/core-win32-x64-msvc": "npm:1.10.6"
|
||||
"@swc/counter": "npm:^0.1.3"
|
||||
"@swc/types": "npm:^0.1.17"
|
||||
peerDependencies:
|
||||
"@swc/helpers": "*"
|
||||
dependenciesMeta:
|
||||
"@swc/core-darwin-arm64":
|
||||
optional: true
|
||||
"@swc/core-darwin-x64":
|
||||
optional: true
|
||||
"@swc/core-linux-arm-gnueabihf":
|
||||
optional: true
|
||||
"@swc/core-linux-arm64-gnu":
|
||||
optional: true
|
||||
"@swc/core-linux-arm64-musl":
|
||||
optional: true
|
||||
"@swc/core-linux-x64-gnu":
|
||||
optional: true
|
||||
"@swc/core-linux-x64-musl":
|
||||
optional: true
|
||||
"@swc/core-win32-arm64-msvc":
|
||||
optional: true
|
||||
"@swc/core-win32-ia32-msvc":
|
||||
optional: true
|
||||
"@swc/core-win32-x64-msvc":
|
||||
optional: true
|
||||
peerDependenciesMeta:
|
||||
"@swc/helpers":
|
||||
optional: true
|
||||
checksum: 10/51eccbba6ee8a41f57a6ba4213ec05859434f52fd6698f508c92a8bb467afda56a1e95b07985faf059b27cb28e77d479340de7210a8616976ea82f774a6d86a3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/counter@npm:^0.1.3":
|
||||
version: 0.1.3
|
||||
resolution: "@swc/counter@npm:0.1.3"
|
||||
@@ -19338,6 +19486,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/types@npm:^0.1.17":
|
||||
version: 0.1.17
|
||||
resolution: "@swc/types@npm:0.1.17"
|
||||
dependencies:
|
||||
"@swc/counter": "npm:^0.1.3"
|
||||
checksum: 10/ddef1ad5bfead3acdfc41f14e79ba43a99200eb325afbad5716058dbe36358b0513400e9f22aff32432be84a98ae93df95a20b94192f69b8687144270e4eaa18
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/types@npm:^0.1.21":
|
||||
version: 0.1.21
|
||||
resolution: "@swc/types@npm:0.1.21"
|
||||
@@ -20659,7 +20816,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/node@npm:*, @types/node@npm:>=12, @types/node@npm:>=12.0.0, @types/node@npm:>=13.7.0, @types/node@npm:>=18.0.0, @types/node@npm:^22.0.0":
|
||||
"@types/node@npm:*, @types/node@npm:>=13.7.0, @types/node@npm:^22.0.0":
|
||||
version: 22.10.5
|
||||
resolution: "@types/node@npm:22.10.5"
|
||||
dependencies:
|
||||
undici-types: "npm:~6.20.0"
|
||||
checksum: 10/a5366961ffa9921e8f15435bc18ea9f8b7a7bb6b3d92dd5e93ebcd25e8af65708872bd8e6fee274b4655bab9ca80fbff9f0e42b5b53857790f13cf68cf4cbbfc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/node@npm:>=12, @types/node@npm:>=12.0.0, @types/node@npm:>=18.0.0":
|
||||
version: 22.13.10
|
||||
resolution: "@types/node@npm:22.13.10"
|
||||
dependencies:
|
||||
@@ -24011,13 +24177,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"async@npm:^3.2.2, async@npm:^3.2.3, async@npm:^3.2.4, async@npm:^3.2.6":
|
||||
"async@npm:^3.2.2, async@npm:^3.2.6":
|
||||
version: 3.2.6
|
||||
resolution: "async@npm:3.2.6"
|
||||
checksum: 10/cb6e0561a3c01c4b56a799cc8bab6ea5fef45f069ab32500b6e19508db270ef2dffa55e5aed5865c5526e9907b1f8be61b27530823b411ffafb5e1538c86c368
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"async@npm:^3.2.3, async@npm:^3.2.4":
|
||||
version: 3.2.4
|
||||
resolution: "async@npm:3.2.4"
|
||||
checksum: 10/bebb5dc2258c45b83fa1d3be179ae0eb468e1646a62d443c8d60a45e84041b28fccebe1e2d1f234bfc3dcad44e73dcdbf4ba63d98327c9f6556e3dbd47c2ae8b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"asynckit@npm:^0.4.0":
|
||||
version: 0.4.0
|
||||
resolution: "asynckit@npm:0.4.0"
|
||||
@@ -24509,13 +24682,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"before-after-hook@npm:^2.1.0, before-after-hook@npm:^2.2.0":
|
||||
"before-after-hook@npm:^2.1.0":
|
||||
version: 2.2.3
|
||||
resolution: "before-after-hook@npm:2.2.3"
|
||||
checksum: 10/e676f769dbc4abcf4b3317db2fd2badb4a92c0710e0a7da12cf14b59c3482d4febf835ad7de7874499060fd4e13adf0191628e504728b3c5bb4ec7a878c09940
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"before-after-hook@npm:^2.2.0":
|
||||
version: 2.2.2
|
||||
resolution: "before-after-hook@npm:2.2.2"
|
||||
checksum: 10/34c190def503f771f8811db0bd0c62b35301fe6059c8d847664633ce0548e8253e2661104ba66c71a85548746ba87d5ff2ebf5278c1f3ad367d111ffc9a26bb4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"better-opn@npm:^3.0.2":
|
||||
version: 3.0.2
|
||||
resolution: "better-opn@npm:3.0.2"
|
||||
@@ -29865,6 +30045,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"express-rate-limit@npm:^7.4.0":
|
||||
version: 7.4.0
|
||||
resolution: "express-rate-limit@npm:7.4.0"
|
||||
peerDependencies:
|
||||
express: 4 || 5 || ^5.0.0-beta.1
|
||||
checksum: 10/33178c652bb1472aad2022194b5cd7963bd3e74d3eaf5e49eb1491a968fdce54551cc76b097ac10d3a1646d62cec2e6f2405ccef5ef5b60152a0c4a148749a4d
|
||||
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"
|
||||
@@ -31040,7 +31229,25 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.3.0":
|
||||
"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6":
|
||||
version: 1.2.6
|
||||
resolution: "get-intrinsic@npm:1.2.6"
|
||||
dependencies:
|
||||
call-bind-apply-helpers: "npm:^1.0.1"
|
||||
dunder-proto: "npm:^1.0.0"
|
||||
es-define-property: "npm:^1.0.1"
|
||||
es-errors: "npm:^1.3.0"
|
||||
es-object-atoms: "npm:^1.0.0"
|
||||
function-bind: "npm:^1.1.2"
|
||||
gopd: "npm:^1.2.0"
|
||||
has-symbols: "npm:^1.1.0"
|
||||
hasown: "npm:^2.0.2"
|
||||
math-intrinsics: "npm:^1.0.0"
|
||||
checksum: 10/a1ffae6d7893a6fa0f4d1472adbc85095edd6b3b0943ead97c3738539cecb19d422ff4d48009eed8c3c27ad678c2b1e38a83b1a1e96b691d13ed8ecefca1068d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.3.0":
|
||||
version: 1.3.0
|
||||
resolution: "get-intrinsic@npm:1.3.0"
|
||||
dependencies:
|
||||
@@ -36734,7 +36941,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"math-intrinsics@npm:^1.1.0":
|
||||
"math-intrinsics@npm:^1.0.0, math-intrinsics@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "math-intrinsics@npm:1.1.0"
|
||||
checksum: 10/11df2eda46d092a6035479632e1ec865b8134bdfc4bd9e571a656f4191525404f13a283a515938c3a8de934dbfd9c09674d9da9fa831e6eb7e22b50b197d2edd
|
||||
@@ -41835,6 +42042,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