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:
Heikki Hellgren
2024-09-17 08:56:38 +03:00
committed by Hellgren Heikki
parent 58cb0452a9
commit 1220cf84d0
10 changed files with 558 additions and 17 deletions
+15
View File
@@ -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
View File
@@ -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.
+2
View File
@@ -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.
*
@@ -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);
});
});
@@ -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';
@@ -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());
+225 -9
View File
@@ -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"