Skip to content

Commit d34a486

Browse files
authored
Merge pull request #999 from kriswest/997-rate-limiter-config
feat: rate limiter config
2 parents 9ad2d07 + aef5313 commit d34a486

File tree

7 files changed

+298
-99
lines changed

7 files changed

+298
-99
lines changed

config.schema.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,30 @@
2424
"description": "Provide domains to use alternative to the defaults",
2525
"type": "object"
2626
},
27+
"rateLimit": {
28+
"description": "API Rate limiting configuration.",
29+
"type": "object",
30+
"properties": {
31+
"windowMs": {
32+
"type": "number",
33+
"description": "How long to remember requests for, in milliseconds (default 10 mins)."
34+
},
35+
"limit": {
36+
"type": "number",
37+
"description": "How many requests to allow (default 150)."
38+
},
39+
"statusCode": {
40+
"type": "number",
41+
"description": "HTTP status code after limit is reached (default is 429)."
42+
},
43+
"message": {
44+
"type": "string",
45+
"description": "Response to return after limit is reached."
46+
}
47+
},
48+
"required": ["windowMs", "limit"],
49+
"additionalProperties": false
50+
},
2751
"privateOrganizations": {
2852
"description": "Pattern searches for listed private organizations are disabled",
2953
"type": "array"

proxy.config.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
"proxyUrl": "https://github.com",
33
"cookieSecret": "cookie secret",
44
"sessionMaxAgeHours": 12,
5+
"rateLimit": {
6+
"windowMs": 60000,
7+
"limit": 150
8+
},
59
"tempPassword": {
610
"sendEmail": false,
711
"emailConfig": {}

src/config/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
Authentication,
77
AuthorisedRepo,
88
Database,
9+
RateLimitConfig,
910
TempPasswordConfig,
1011
UserSettings,
1112
} from './types';
@@ -30,6 +31,8 @@ let _urlShortener: string = defaultSettings.urlShortener;
3031
let _contactEmail: string = defaultSettings.contactEmail;
3132
let _csrfProtection: boolean = defaultSettings.csrfProtection;
3233
let _domains: Record<string, unknown> = defaultSettings.domains;
34+
let _rateLimit: RateLimitConfig = defaultSettings.rateLimit;
35+
3336
// These are not always present in the default config file, so casting is required
3437
let _tlsEnabled = defaultSettings.tls.enabled;
3538
let _tlsKeyPemPath = defaultSettings.tls.key;
@@ -99,6 +102,7 @@ export const logConfiguration = () => {
99102
console.log(`authorisedList = ${JSON.stringify(getAuthorisedList())}`);
100103
console.log(`data sink = ${JSON.stringify(getDatabase())}`);
101104
console.log(`authentication = ${JSON.stringify(getAuthentication())}`);
105+
console.log(`rateLimit = ${JSON.stringify(getRateLimit())}`);
102106
};
103107

104108
export const getAPIs = () => {
@@ -217,3 +221,10 @@ export const getDomains = () => {
217221
}
218222
return _domains;
219223
};
224+
225+
export const getRateLimit = () => {
226+
if (_userSettings && _userSettings.rateLimit) {
227+
_rateLimit = _userSettings.rateLimit;
228+
}
229+
return _rateLimit;
230+
};

src/config/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Options as RateLimitOptions } from 'express-rate-limit';
2+
13
export interface UserSettings {
24
authorisedList: AuthorisedRepo[];
35
sink: Database[];
@@ -18,6 +20,7 @@ export interface UserSettings {
1820
contactEmail: string;
1921
csrfProtection: boolean;
2022
domains: Record<string, unknown>;
23+
rateLimit: RateLimitConfig;
2124
}
2225

2326
export interface TLSConfig {
@@ -50,3 +53,7 @@ export interface TempPasswordConfig {
5053
sendEmail: boolean;
5154
emailConfig: Record<string, unknown>;
5255
}
56+
57+
export type RateLimitConfig = Partial<
58+
Pick<RateLimitOptions, 'windowMs' | 'limit' | 'message' | 'statusCode'>
59+
>;

src/service/index.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@ const db = require('../db');
99
const rateLimit = require('express-rate-limit');
1010
const lusca = require('lusca');
1111

12-
const limiter = rateLimit({
13-
windowMs: 15 * 60 * 1000, // 15 minutes
14-
max: 100, // limit each IP to 100 requests per windowMs
15-
});
12+
const limiter = rateLimit(config.getRateLimit());
1613

1714
const { GIT_PROXY_UI_PORT: uiPort } = require('../config/env').serverConfig;
1815

test/testConfig.test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ describe('default configuration', function () {
1616
expect(config.getDatabase()).to.be.eql(defaultSettings.sink[0]);
1717
expect(config.getTempPasswordConfig()).to.be.eql(defaultSettings.tempPassword);
1818
expect(config.getAuthorisedList()).to.be.eql(defaultSettings.authorisedList);
19+
expect(config.getRateLimit()).to.be.eql(defaultSettings.rateLimit);
1920
expect(config.getTLSKeyPemPath()).to.be.eql(defaultSettings.tls.key);
2021
expect(config.getTLSCertPemPath()).to.be.eql(defaultSettings.tls.cert);
2122
});
@@ -107,6 +108,21 @@ describe('user configuration', function () {
107108
expect(config.getTLSCertPemPath()).to.be.eql(user.tls.cert);
108109
});
109110

111+
it('should override default settings for rate limiting', function () {
112+
const limitConfig = {
113+
rateLimit: {
114+
windowMs: 60000,
115+
limit: 1500,
116+
},
117+
};
118+
fs.writeFileSync(tempUserFile, JSON.stringify(limitConfig));
119+
120+
const config = require('../src/config');
121+
122+
expect(config.getRateLimit().windowMs).to.be.eql(limitConfig.rateLimit.windowMs);
123+
expect(config.getRateLimit().limit).to.be.eql(limitConfig.rateLimit.limit);
124+
});
125+
110126
afterEach(function () {
111127
fs.rmSync(tempUserFile);
112128
fs.rmdirSync(tempDir);

0 commit comments

Comments
 (0)