Skip to content

Commit

Permalink
Merge pull request #6347 from NMDSdevopsServiceAdm/fix/rate-limiting
Browse files Browse the repository at this point in the history
Fix/rate limiting
  • Loading branch information
duncanc19 authored Sep 9, 2024
2 parents dca3fb0 + 36c8f07 commit 982eac6
Show file tree
Hide file tree
Showing 8 changed files with 512 additions and 15,013 deletions.
15,454 changes: 473 additions & 14,981 deletions backend/package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,10 @@
"exceljs": "^4.2.0",
"express": "^4.19.2",
"express-http-proxy": "^1.6.2",
"express-rate-limit": "^5.5.1",
"express-rate-limit": "^7.4.0",
"express-sanitizer": "^1.0.6",
"helmet": "^4.6.0",
"ioredis": "^5.4.1",
"is-ci": "^3.0.0",
"js-yaml": "^3.14.1",
"jsonwebtoken": "^9.0.2",
Expand All @@ -83,7 +84,7 @@
"pg": "^8.5.1",
"pg-hstore": "^2.3.4",
"pug": "^3.0.1",
"rate-limit-redis": "^2.1.0",
"rate-limit-redis": "^4.2.0",
"request": "^2.88.2",
"sequelize": "^6.29.0",
"sequelize-cli": "^6.6.0",
Expand Down
4 changes: 1 addition & 3 deletions backend/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,9 +297,7 @@ app.get('/loaderio-63e80cd3c669177f22e9ec997ea2594d.txt', function (req, res) {
});

app.use('*', authLimiter);
// app.get('*', function (req, res) {
// return res.sendFile(path.join(__dirname, 'dist/index.html'));
// });
app.set('trust proxy', 2 /* number of proxies between user and server */);

app.all('*', function (req, res) {
res.status(404);
Expand Down
4 changes: 2 additions & 2 deletions backend/server/routes/nhsBsaApi/apiDocs.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const express = require('express');
const router = express.Router();
const fs = require('fs');
const path = require('path');
const { authLimiter } = require('../../utils/middleware/rateLimiting');
const { nhsBsaApiLimiter } = require('../../utils/middleware/rateLimitingNHSBSAAPI');

const nhsBsaApiDocumentation = (req, res) => {
try {
Expand All @@ -17,6 +17,6 @@ const nhsBsaApiDocumentation = (req, res) => {
}
};

router.route('/').get(authLimiter, nhsBsaApiDocumentation);
router.route('/').get(nhsBsaApiLimiter, nhsBsaApiDocumentation);
module.exports = router;
module.exports.nhsBsaApiDocumentation = nhsBsaApiDocumentation;
3 changes: 2 additions & 1 deletion backend/server/routes/nhsBsaApi/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ const express = require('express');
const router = express.Router();
const generateJWT = require('../../utils/security/NHSBSAgenerateJWT');
const config = require('../../config/config');
const { nhsBsaApiLimiter } = require('../../utils/middleware/rateLimitingNHSBSAAPI');

router.use('/', nhsBsaApiLimiter);
router.route('/').get(function (req, res) {

const authHeader = req.headers && req.headers.nhsbsaapikey;
const nhsBsaApiKey = config.get('nhsBsaApi.apikey');

Expand Down
4 changes: 2 additions & 2 deletions backend/server/routes/nhsBsaApi/workplaceData.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const express = require('express');
const router = express.Router();
const models = require('../../models');
const authorization = require('../../utils/middleware/isNHSBSAAuthenticated');
const { authLimiter } = require('../../utils/middleware/rateLimiting');
const { nhsBsaApiLimiter } = require('../../utils/middleware/rateLimitingNHSBSAAPI');
// WDF effective date
const WdfCalculator = require('../../models/classes/wdfCalculator').WdfCalculator;

Expand Down Expand Up @@ -98,7 +98,7 @@ const calculatePercentageOfWorkersEligible = (workers) => {
return Math.floor((numberOfEligibleWorkers / numberOfWorkers) * 100);
};

router.route('/:workplaceId').get(authLimiter, authorization.isAuthorised, nhsBsaApi);
router.route('/:workplaceId').get(nhsBsaApiLimiter, authorization.isAuthorised, nhsBsaApi);
module.exports = router;
module.exports.nhsBsaApi = nhsBsaApi;
module.exports.subsidiariesList = subsidiariesList;
27 changes: 18 additions & 9 deletions backend/server/utils/middleware/rateLimiting.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
const config = require('../../config/config');
const RateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const expressRateLimit = require('express-rate-limit');
const { RedisStore } = require('rate-limit-redis');
const RedisClient = require('ioredis');
const isCI = require('is-ci');

const store = isCI
const redisClient = new RedisClient(config.get('redis.url'));
const authStore = isCI
? undefined
: new RedisStore({
redisURL: config.get('redis.url'),
sendCommand: (...args) => redisClient.call(...args),
});

const dbStore = isCI
? undefined
: new RedisStore({
sendCommand: (...args) => redisClient.call(...args),
});

const rateLimiterConfig = {
store,
delayMs: 0, // disable delaying - full speed until the max limit is reached
passIfNotConnected: true,
windowMs: 5000,
Expand All @@ -20,19 +27,21 @@ const authLimiter = isCI
? (req, res, next) => {
next();
}
: new RateLimit({
: expressRateLimit.rateLimit({
...rateLimiterConfig,
max: 1000,
store: authStore,
limit: 1000,
prefix: 'auth:',
});

const dbLimiter = isCI
? (req, res, next) => {
next();
}
: new RateLimit({
: expressRateLimit.rateLimit({
...rateLimiterConfig,
max: 1000,
store: dbStore,
limit: 1000,
prefix: 'db:',
});

Expand Down
24 changes: 11 additions & 13 deletions backend/server/utils/middleware/rateLimitingNHSBSAAPI.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,31 @@

const config = require('../../config/config');
const RateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const expressRateLimit = require('express-rate-limit');
const { RedisStore } = require('rate-limit-redis');
const RedisClient = require('ioredis');
const isCI = require('is-ci');

const redisClient = new RedisClient(config.get('redis.url'));
const store = isCI
? undefined
: new RedisStore({
redisURL: config.get('redis.url'),
sendCommand: (...args) => redisClient.call(...args),
});

const rateLimiterConfig = {
store,
delayMs: 0, // disable delaying - full speed until the max limit is reached
passIfNotConnected: true,
windowMs: 60 * 1000, // 1 minute
windowMs: 60 * 1000, // 1 minute
};

const authLimiter = isCI
const nhsBsaApiLimiter = isCI
? (req, res, next) => {
next();
}
: new RateLimit({
: expressRateLimit.rateLimit({
...rateLimiterConfig,
max: 100, // maximum number of requests allowed in the windowMs
prefix: 'auth:',
limit: 200, // maximum number of requests allowed in the windowMs
prefix: 'nhsBsaApi:',
});



module.exports.authLimiter = authLimiter;

module.exports.nhsBsaApiLimiter = nhsBsaApiLimiter;

0 comments on commit 982eac6

Please sign in to comment.