From df68e6ee797c8b5221259efa63f1c8504aa860ed Mon Sep 17 00:00:00 2001 From: Marc Fornos Date: Mon, 17 Jun 2024 18:42:11 +0200 Subject: [PATCH] add rate limit #91 --- packages/server/package.json | 1 + packages/server/src/environment.ts | 6 +++ packages/server/src/server.ts | 2 + packages/server/src/services/index.ts | 15 +++++++- .../src/services/ingress/server/server.ts | 2 +- packages/server/src/services/limit.ts | 37 +++++++++++++++++++ yarn.lock | 14 ++++++- 7 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 packages/server/src/services/limit.ts diff --git a/packages/server/package.json b/packages/server/package.json index 7b5ae707..81c06e72 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -73,6 +73,7 @@ "dependencies": { "@fastify/cors": "^9.0.1", "@fastify/jwt": "^8.0.1", + "@fastify/rate-limit": "^9.1.0", "@fastify/swagger": "^8.14.0", "@fastify/swagger-ui": "^4.0.0", "@fastify/websocket": "^10.0.1", diff --git a/packages/server/src/environment.ts b/packages/server/src/environment.ts index 517f472d..564749ab 100644 --- a/packages/server/src/environment.ts +++ b/packages/server/src/environment.ts @@ -1,6 +1,12 @@ /* istanbul ignore next */ export const environment = process.env.NODE_ENV || 'development' +const NON_PROD = ['test', 'development'] + +export function isNonProdEnv(envName: string): boolean { + return NON_PROD.includes(envName) +} + const envToLogger: Record = { development: { transport: { diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts index 27093ce5..e967748e 100644 --- a/packages/server/src/server.ts +++ b/packages/server/src/server.ts @@ -20,6 +20,7 @@ import { Configuration, Connector, Ingress, + Limit, Persistence, Root, Subscriptions, @@ -151,6 +152,7 @@ export async function createServer(opts: ServerOptions) { await server.register(FastifyCors, corsOpts) } + await server.register(Limit) await server.register(Auth) await server.register(Root) diff --git a/packages/server/src/services/index.ts b/packages/server/src/services/index.ts index 01927b8e..84f99474 100644 --- a/packages/server/src/services/index.ts +++ b/packages/server/src/services/index.ts @@ -3,6 +3,7 @@ import Agents from './agents/plugin.js' import Auth from './auth.js' import Configuration from './config.js' import Ingress from './ingress/consumer/plugin.js' +import Limit from './limit.js' import Connector from './networking/plugin.js' import Persistence from './persistence/plugin.js' import Root from './root.js' @@ -11,4 +12,16 @@ import Telemetry from './telemetry/plugin.js' export * from './types.js' -export { Root, Auth, Administration, Persistence, Connector, Configuration, Agents, Subscriptions, Telemetry, Ingress } +export { + Root, + Limit, + Auth, + Administration, + Persistence, + Connector, + Configuration, + Agents, + Subscriptions, + Telemetry, + Ingress, +} diff --git a/packages/server/src/services/ingress/server/server.ts b/packages/server/src/services/ingress/server/server.ts index e7f608e0..772a4977 100644 --- a/packages/server/src/services/ingress/server/server.ts +++ b/packages/server/src/services/ingress/server/server.ts @@ -69,8 +69,8 @@ export async function createIngressServer(opts: ServerOptions) { exposeUptime: true, }) - await server.register(Root) await server.register(Auth) + await server.register(Root) await server.register(Configuration, opts) await server.register(Connector) await server.register(Persistence, opts) diff --git a/packages/server/src/services/limit.ts b/packages/server/src/services/limit.ts new file mode 100644 index 00000000..fecb13a7 --- /dev/null +++ b/packages/server/src/services/limit.ts @@ -0,0 +1,37 @@ +import { FastifyPluginAsync } from 'fastify' +import fp from 'fastify-plugin' + +import rateLimit from '@fastify/rate-limit' + +import { environment, isNonProdEnv } from '../environment.js' + +const limitPlugin: FastifyPluginAsync = async (fastify) => { + if (isNonProdEnv(environment)) { + fastify.log.warn('(!) Rate limits are disabled [%s]', environment) + + return + } + + // TODO: make it configurable + await fastify.register(rateLimit, { + global: true, + max: 2, + timeWindow: 1000, + hook: 'preHandler', + /*keyGenerator: function (request) { + return request.headers['x-real-ip'] // nginx + || request.headers['x-client-ip'] // apache + || request.ip*/ + }) + + fastify.setNotFoundHandler( + { + preHandler: fastify.rateLimit(), + }, + function (_, reply) { + reply.code(404).send() + } + ) +} + +export default fp(limitPlugin) diff --git a/yarn.lock b/yarn.lock index 6c40b216..e964c342 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1158,6 +1158,17 @@ __metadata: languageName: node linkType: hard +"@fastify/rate-limit@npm:^9.1.0": + version: 9.1.0 + resolution: "@fastify/rate-limit@npm:9.1.0" + dependencies: + "@lukeed/ms": "npm:^2.0.1" + fastify-plugin: "npm:^4.0.0" + toad-cache: "npm:^3.3.1" + checksum: 10c0/55914e4af5d93fff29e94ad4e3f369a371a3e7b97e501e796167dd49115c5d8dfa94aec8ec78ffcdb058268465b072a0892880523774b7b90a27a8ff304fea82 + languageName: node + linkType: hard + "@fastify/send@npm:^2.0.0": version: 2.1.0 resolution: "@fastify/send@npm:2.1.0" @@ -2528,6 +2539,7 @@ __metadata: "@biomejs/biome": "npm:1.8.1" "@fastify/cors": "npm:^9.0.1" "@fastify/jwt": "npm:^8.0.1" + "@fastify/rate-limit": "npm:^9.1.0" "@fastify/swagger": "npm:^8.14.0" "@fastify/swagger-ui": "npm:^4.0.0" "@fastify/websocket": "npm:^10.0.1" @@ -7961,7 +7973,7 @@ __metadata: languageName: node linkType: hard -"toad-cache@npm:^3.3.0": +"toad-cache@npm:^3.3.0, toad-cache@npm:^3.3.1": version: 3.7.0 resolution: "toad-cache@npm:3.7.0" checksum: 10c0/7dae2782ee20b22c9798bb8b71dec7ec6a0091021d2ea9dd6e8afccab6b65b358fdba49a02209fac574499702e2c000660721516c87c2538d1b2c0ba03e8c0c3