Skip to content

Commit 3821bd0

Browse files
chore/Add-rate-limit (#44)
* chore/Add-rate-limit * Update README * Rebase with main * Fix comments
1 parent b9b87d9 commit 3821bd0

File tree

10 files changed

+70
-43
lines changed

10 files changed

+70
-43
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ REDIS_USERNAME=''
2323
JOBS_RETENTION_HOURS=24
2424

2525
OTP_EXPIRATION_MINUTES=15
26+
ENABLE_RATE_LIMIT='true'

.github/workflows/node.js.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ env:
3030
REDIS_USERNAME: 'default'
3131
JOBS_RETENTION_HOURS: '24'
3232
OTP_EXPIRATION_MINUTES: '15'
33+
ENABLE_RATE_LIMIT: 'true'
3334

3435
jobs:
3536
build:

.woodpecker/.backend-ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ x-common: &common
2222
- REDIS_USERNAME=default
2323
- JOBS_RETENTION_HOURS=24
2424
- OTP_EXPIRATION_MINUTES=15
25+
- ENABLE_RATE_LIMIT=true
2526

2627
pipeline:
2728
setup:

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,7 @@ In fact, the Dockerfile has instructions only for generating the production-read
9595
### BullMQ worker
9696
* To add a new worker we just need to create a `new Worker()` object in the worker folder and pass a queue name to pick up tasks from.
9797
* To schedule a new job in the queue we need to create a `new Queue()` object in the queue folder and pass it a queue name to schedule tasks in. Any metadata for the task should be pass as a JSON, e.g `queue.add('job_name', { ...params }, options);`.
98+
99+
### Rate limit
100+
* To use express rate limit set the `ENABLE_RATE_LIMIT` env var to true otherwise rate limits will dependant on the nginx configuration.
101+
* To add a new rate limit we need to create a new `rateLimit` object and then assign it to an endpoint e.g `app.use('v1/auth/register', rateLimit)`. For more info check out the [express-rate-limit docs](https://express-rate-limit.mintlify.app/overview).

package-lock.json

Lines changed: 38 additions & 35 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
"dotenv": "^16.0.0",
7575
"dotenv-cli": "^7.3.0",
7676
"express": "^4.17.1",
77-
"express-rate-limit": "^6.3.0",
77+
"express-rate-limit": "^7.1.5",
7878
"helmet": "^7.0.0",
7979
"http-status": "^1.5.0",
8080
"ioredis": "^5.3.2",
@@ -88,6 +88,7 @@
8888
"nodemon": "^3.0.1",
8989
"prisma": "^5.5.2",
9090
"pug": "^3.0.2",
91+
"rate-limit-redis": "^4.2.0",
9192
"swagger-ui-express": "^5.0.0",
9293
"tsconfig-paths": "^4.1.2",
9394
"tsoa": "^5.1.1",

src/config/config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ const envVarsSchema = z
5454
(val) => !Number.isNaN(val),
5555
'OTP EXPIRATION TIME must be a number',
5656
),
57+
ENABLE_RATE_LIMIT: z.string(),
5758
})
5859
.passthrough();
5960

@@ -62,6 +63,8 @@ const envVars = envVarsSchema.parse(process.env);
6263
export const isDevelopment = envVars.NODE_ENV === 'development';
6364
export const isTest = envVars.NODE_ENV === 'test';
6465
export const isProduction = envVars.NODE_ENV === 'production';
66+
export const hasToApplyRateLimit =
67+
envVars.ENABLE_RATE_LIMIT.toLocaleLowerCase() === 'true';
6568

6669
export const config: Config = {
6770
env: envVars.NODE_ENV,

src/middlewares/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import express, { Application } from 'express';
22
import helmet from 'helmet';
33
import compression from 'compression';
44
import cors from 'cors';
5-
import { globalLimiter } from 'middlewares/rateLimiter';
5+
import { applyRateLimit } from 'middlewares/rateLimiter';
66
import { morganHandlers } from 'config/morgan';
77
import { errorConverter, errorHandler } from 'middlewares/error';
88
import { Wrapper } from 'types';
@@ -24,7 +24,7 @@ export const preRoutesMiddleware = (app: Application) => {
2424
app.use(cors());
2525

2626
// Limit requests from same IP
27-
app.use(globalLimiter);
27+
applyRateLimit(app);
2828

2929
// Load Morgan handlers
3030
app.use(morganHandlers.successHandler);

src/middlewares/rateLimiter.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
1+
import { Application } from 'express';
12
import rateLimit from 'express-rate-limit';
3+
import { RedisStore } from 'rate-limit-redis';
24

3-
export const globalLimiter = rateLimit({
4-
windowMs: 15 * 60 * 1000,
5-
max: 20,
6-
skipSuccessfulRequests: true,
5+
import { redisConnection } from 'utils/redis';
6+
import { hasToApplyRateLimit } from 'config/config';
7+
8+
// Defaults to window of 1 minute and a limit of 5 connections
9+
const authLimiter = rateLimit({
10+
store: new RedisStore({
11+
// @ts-expect-error - Known issue: the `call` function is not present in @types/ioredis
12+
sendCommand: (...args: string[]) => redisConnection.call(...args),
13+
}),
714
});
15+
16+
export const applyRateLimit = (app: Application) => {
17+
if (hasToApplyRateLimit) {
18+
// This will make all the routes under the auth controller share the same rate limit
19+
app.use('*/auth/', authLimiter);
20+
}
21+
};

src/utils/redis.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ const workerConnectionOptions = {
77
password: config.redisPassword,
88
username: config.redisUsername,
99
maxRetriesPerRequest: null,
10-
enableOfflineQueue: false,
1110
showFriendlyErrorStack: !isProduction,
1211
} as RedisOptions;
1312

0 commit comments

Comments
 (0)