Merge pull request #21327 from overleaf/msm-optional-subnet-rate-limiter

[web] Add option to disable subnet rate limiting (+CE/SP Hotfix `5.2.1`)

GitOrigin-RevId: 78d60c9638cede729dd93c3c2421f55b34c0dbfe
This commit is contained in:
Miguel Serrano 2024-10-28 12:37:54 +01:00 committed by Copybot
parent 25f4e6cf67
commit 27c2e8b938
6 changed files with 81 additions and 1 deletions

View file

@ -212,6 +212,11 @@ const settings = {
enabled: process.env.OVERLEAF_CSP_ENABLED !== 'false', enabled: process.env.OVERLEAF_CSP_ENABLED !== 'false',
}, },
rateLimit: {
subnetRateLimiterDisabled:
process.env.SUBNET_RATE_LIMITER_DISABLED !== 'false',
},
// These credentials are used for authenticating api requests // These credentials are used for authenticating api requests
// between services that may need to go over public channels // between services that may need to go over public channels
httpAuthUsers, httpAuthUsers,

View file

@ -0,0 +1,5 @@
FROM sharelatex/sharelatex:5.2.0
# Subnet rate limiter fix
COPY pr_21327.patch /
RUN cd / && patch -p0 < pr_21327.patch && rm pr_21327.patch

View file

@ -0,0 +1,36 @@
--- overleaf/services/web/app/src/infrastructure/RateLimiter.js
+++ overleaf/services/web/app/src/infrastructure/RateLimiter.js
@@ -39,7 +39,7 @@ class RateLimiter {
keyPrefix: `rate-limit:${name}`,
storeClient: rclient,
})
- if (opts.subnetPoints) {
+ if (opts.subnetPoints && !Settings.rateLimit?.subnetRateLimiterDisabled) {
this._subnetRateLimiter = new RateLimiterFlexible.RateLimiterRedis({
...opts,
points: opts.subnetPoints,
--- overleaf/services/web/config/settings.defaults.js
+++ overleaf/services/web/config/settings.defaults.js
@@ -777,6 +777,8 @@ module.exports = {
reloadModuleViewsOnEachRequest: process.env.NODE_ENV === 'development',
rateLimit: {
+ subnetRateLimiterDisabled:
+ process.env.SUBNET_RATE_LIMITER_DISABLED === 'true',
autoCompile: {
everyone: process.env.RATE_LIMIT_AUTO_COMPILE_EVERYONE || 100,
standard: process.env.RATE_LIMIT_AUTO_COMPILE_STANDARD || 25,
--- etc/overleaf/settings.js
+++ etc/overleaf/settings.js
@@ -212,6 +212,11 @@ const settings = {
enabled: process.env.OVERLEAF_CSP_ENABLED !== 'false',
},
+ rateLimit: {
+ subnetRateLimiterDisabled:
+ process.env.SUBNET_RATE_LIMITER_DISABLED !== 'false',
+ },
+
// These credentials are used for authenticating api requests
// between services that may need to go over public channels
httpAuthUsers,

View file

@ -39,7 +39,7 @@ class RateLimiter {
keyPrefix: `rate-limit:${name}`, keyPrefix: `rate-limit:${name}`,
storeClient: rclient, storeClient: rclient,
}) })
if (opts.subnetPoints) { if (opts.subnetPoints && !Settings.rateLimit?.subnetRateLimiterDisabled) {
this._subnetRateLimiter = new RateLimiterFlexible.RateLimiterRedis({ this._subnetRateLimiter = new RateLimiterFlexible.RateLimiterRedis({
...opts, ...opts,
points: opts.subnetPoints, points: opts.subnetPoints,

View file

@ -777,6 +777,8 @@ module.exports = {
reloadModuleViewsOnEachRequest: process.env.NODE_ENV === 'development', reloadModuleViewsOnEachRequest: process.env.NODE_ENV === 'development',
rateLimit: { rateLimit: {
subnetRateLimiterDisabled:
process.env.SUBNET_RATE_LIMITER_DISABLED === 'true',
autoCompile: { autoCompile: {
everyone: process.env.RATE_LIMIT_AUTO_COMPILE_EVERYONE || 100, everyone: process.env.RATE_LIMIT_AUTO_COMPILE_EVERYONE || 100,
standard: process.env.RATE_LIMIT_AUTO_COMPILE_STANDARD || 25, standard: process.env.RATE_LIMIT_AUTO_COMPILE_STANDARD || 25,

View file

@ -19,11 +19,13 @@ describe('RateLimiter', function () {
this.RateLimiterFlexible = { this.RateLimiterFlexible = {
RateLimiterRedis: sinon.stub(), RateLimiterRedis: sinon.stub(),
} }
this.Settings = {}
this.RateLimiter = SandboxedModule.require(modulePath, { this.RateLimiter = SandboxedModule.require(modulePath, {
requires: { requires: {
'./RedisWrapper': this.RedisWrapper, './RedisWrapper': this.RedisWrapper,
'rate-limiter-flexible': this.RateLimiterFlexible, 'rate-limiter-flexible': this.RateLimiterFlexible,
'@overleaf/settings': this.Settings,
}, },
}) })
}) })
@ -60,4 +62,34 @@ describe('RateLimiter', function () {
).to.throw() ).to.throw()
}) })
}) })
describe('_subnetRateLimiter', function () {
it('should be defined by default', function () {
const rateLimiter = new this.RateLimiter.RateLimiter('some-limit', {
points: 20,
subnetPoints: 200,
duration: 60,
})
expect(rateLimiter._subnetRateLimiter).not.to.be.undefined
})
it('should be undefined when subnet rate limiting is disabled', function () {
this.Settings.rateLimit = { subnetRateLimiterDisabled: true }
const rateLimiter = new this.RateLimiter.RateLimiter('some-limit', {
points: 20,
subnetPoints: 200,
duration: 60,
})
expect(rateLimiter._subnetRateLimiter).to.be.undefined
})
it('should be undefined when subnetPoints are not passed as an option', function () {
const rateLimiter = new this.RateLimiter.RateLimiter('some-limit', {
points: 20,
duration: 60,
})
expect(rateLimiter._subnetRateLimiter).to.be.undefined
})
})
}) })