Skip to content

Commit 4f56ed4

Browse files
committed
Add rate limiting to routes
1 parent 04d79a1 commit 4f56ed4

File tree

5 files changed

+37
-6
lines changed

5 files changed

+37
-6
lines changed

apps/api/src/app/controllers/image.controller.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const getUploadSignature = createRoute(routeDefinition.getUploadSignature.valida
2121
const apiKey = cloudinary.config().api_key;
2222
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
2323
const apiSecret = cloudinary.config().api_secret!;
24-
const context = `caption=${userId.replace('|', '\\|')}|environment=${ENV.JETSTREAM_SERVER_URL}`;
24+
const context = `caption=${userId.replaceAll('|', '\\|')}|environment=${ENV.JETSTREAM_SERVER_URL}`;
2525

2626
const signature = cloudinary.utils.api_sign_request({ timestamp, upload_preset: 'jetstream-issues', context }, apiSecret);
2727

apps/api/src/app/routes/route.middleware.ts

+22
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,34 @@ import { ensureBoolean } from '@jetstream/shared/utils';
66
import { ApplicationCookie } from '@jetstream/types';
77
import { addDays, getUnixTime } from 'date-fns';
88
import * as express from 'express';
9+
import { rateLimit } from 'express-rate-limit';
910
import pino from 'pino';
1011
import { v4 as uuid } from 'uuid';
1112
import * as salesforceOrgsDb from '../db/salesforce-org.db';
1213
import { clerkClient, incomingMessageToClerkRequest } from '../services/auth.service';
1314
import { AuthenticationError, NotFoundError, UserFacingError } from '../utils/error-handler';
1415

16+
export const rateLimitStandardMiddleware = rateLimit({
17+
windowMs: 1 * 60 * 1000, // 1 minute
18+
limit: 900, // limit each IP to 1000 requests per minute (15/RPS)
19+
standardHeaders: true,
20+
legacyHeaders: false,
21+
});
22+
23+
export const rateLimitMediumMiddleware = rateLimit({
24+
windowMs: 10 * 60 * 1000, // 10 minutes
25+
limit: 1000, // limit each IP to 1000 requests per 10 minutes
26+
standardHeaders: true,
27+
legacyHeaders: false,
28+
});
29+
30+
export const rateLimitStrictMiddleware = rateLimit({
31+
windowMs: 60 * 60 * 1000, // 1 hour
32+
limit: 100, // limit each IP to 100 requests per hour
33+
standardHeaders: true,
34+
legacyHeaders: false,
35+
});
36+
1537
export function addContextMiddleware(req: express.Request, res: express.Response, next: express.NextFunction) {
1638
res.locals.requestId = res.locals.requestId || uuid();
1739
const clientReqId = req.header(HTTP.HEADERS.X_CLIENT_REQUEST_ID);

apps/api/src/main.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ import {
1717
addContextMiddleware,
1818
blockBotByUserAgentMiddleware,
1919
notFoundMiddleware,
20+
rateLimitMediumMiddleware,
21+
rateLimitStandardMiddleware,
22+
rateLimitStrictMiddleware,
2023
setApplicationCookieMiddleware,
2124
} from './app/routes/route.middleware';
2225
import { blockBotHandler, healthCheck, uncaughtErrorHandler } from './app/utils/response.handlers';
@@ -235,11 +238,11 @@ if (ENV.NODE_ENV === 'production' && cluster.isPrimary) {
235238
app.use(json({ limit: '20mb', verify: rawBodySaver, type: ['json', 'application/csp-report'] }));
236239
app.use(urlencoded({ extended: true }));
237240

238-
app.use('/healthz', healthCheck);
239-
app.use('/api', apiRoutes);
240-
app.use('/static', staticAuthenticatedRoutes); // these are routes that return files or redirect (e.x. NOT JSON)
241-
app.use('/oauth', oauthRoutes); // NOTE: there are also static files with same path
242-
app.use('/webhook', webhookRoutes);
241+
app.use('/healthz', rateLimitStandardMiddleware, healthCheck);
242+
app.use('/api', rateLimitStandardMiddleware, apiRoutes);
243+
app.use('/static', rateLimitMediumMiddleware, staticAuthenticatedRoutes); // these are routes that return files or redirect (e.x. NOT JSON)
244+
app.use('/oauth', rateLimitStrictMiddleware, oauthRoutes); // NOTE: there are also static files with same path
245+
app.use('/webhook', rateLimitMediumMiddleware, webhookRoutes);
243246

244247
if (ENV.ENVIRONMENT !== 'production' || ENV.IS_CI) {
245248
app.use('/test', testRoutes);

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@
275275
"express": "^4.19.2",
276276
"express-http-proxy": "^1.6.2",
277277
"express-promise-router": "^4.1.1",
278+
"express-rate-limit": "^7.4.0",
278279
"fast-xml-parser": "^4.4.0",
279280
"fastify": "^3.29.4",
280281
"file-saver": "^2.0.5",

yarn.lock

+5
Original file line numberDiff line numberDiff line change
@@ -15448,6 +15448,11 @@ express-promise-router@^4.1.1:
1544815448
lodash.flattendeep "^4.0.0"
1544915449
methods "^1.0.0"
1545015450

15451+
express-rate-limit@^7.4.0:
15452+
version "7.4.0"
15453+
resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-7.4.0.tgz#5db412b8de83fa07ddb40f610c585ac8c1dab988"
15454+
integrity sha512-v1204w3cXu5gCDmAvgvzI6qjzZzoMWKnyVDk3ACgfswTQLYiGen+r8w0VnXnGMmzEN/g8fwIQ4JrFFd4ZP6ssg==
15455+
1545115456
express@^4.17.3:
1545215457
version "4.18.1"
1545315458
resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf"

0 commit comments

Comments
 (0)