From e9c7b0d17c9fe5260edf92081c34f16164d10da1 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Tue, 23 Nov 2021 10:02:40 +0000 Subject: [PATCH] Merge pull request #5750 from overleaf/jpa-cache-req-ip [web] cache req.ip and bail out in case none is available GitOrigin-RevId: 07084114676ffd13530c9ad4e0ff386fc2c5fa17 --- services/web/app/src/infrastructure/Server.js | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/services/web/app/src/infrastructure/Server.js b/services/web/app/src/infrastructure/Server.js index e940a4b3ab..61ba7cf355 100644 --- a/services/web/app/src/infrastructure/Server.js +++ b/services/web/app/src/infrastructure/Server.js @@ -76,6 +76,39 @@ if (Settings.behindProxy) { next() }) } + +// `req.ip` is a getter on the underlying socket. +// The socket details are freed as the connection is dropped -- aka aborted. +// Hence `req.ip` may read `undefined` upon connection drop. +// A couple of places require a valid IP at all times. Cache it! +const ORIGINAL_REQ_IP = Object.getOwnPropertyDescriptor( + Object.getPrototypeOf(app.request), + 'ip' +).get +Object.defineProperty(app.request, 'ip', { + configurable: true, + enumerable: true, + get: function ipWithCache() { + const ip = ORIGINAL_REQ_IP.call(this) + // Shadow the prototype level getter with a property on the instance. + // Any future access on `req.ip` will get served by the instance property. + Object.defineProperty(this, 'ip', { value: ip }) + return ip + }, +}) +app.use(function (req, res, next) { + if (req.aborted) { + // Request has been aborted already. + return + } + // Implicitly cache the ip, see above. + if (!req.ip) { + // Critical connection details are missing. + return + } + next() +}) + if (Settings.exposeHostname) { const HOSTNAME = require('os').hostname() app.use((req, res, next) => {