From 6f01a448ad295685d457c7394bd660c0a566ce44 Mon Sep 17 00:00:00 2001 From: theS1LV3R Date: Thu, 23 Jun 2022 23:08:04 +0200 Subject: [PATCH 1/5] feat: get client ip from x-forwarded-for header if available Useful for use-cases where Uptime Kuma is running behind a reverse proxy --- server/server.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/server.js b/server/server.js index 2d3f37ee..e74abaff 100644 --- a/server/server.js +++ b/server/server.js @@ -1677,7 +1677,8 @@ async function shutdownFunction(signal) { } function getClientIp(socket) { - return socket.client.conn.remoteAddress.replace(/^.*:/, ""); + return socket.client.conn.request.headers["x-forwarded-for"] + || socket.client.conn.remoteAddress.replace(/^.*:/, ""); } /** Final function called before application exits */ From 0a368ff55316f2cfbf2f2d3d5292937b42946564 Mon Sep 17 00:00:00 2001 From: Zoe Date: Mon, 4 Jul 2022 20:36:03 +0200 Subject: [PATCH 2/5] feat: add x-real-ip as a secondary header for client ip Now allows both x-forwarded-for as well as x-real-ip to be used for the client ip, preferring x-forwarded-for --- server/server.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/server.js b/server/server.js index e74abaff..476f8664 100644 --- a/server/server.js +++ b/server/server.js @@ -1677,7 +1677,8 @@ async function shutdownFunction(signal) { } function getClientIp(socket) { - return socket.client.conn.request.headers["x-forwarded-for"] + return socket.client.conn.request.headers["x-forwarded-for"] + || socket.client.conn.request.headers["x-real-ip"] || socket.client.conn.remoteAddress.replace(/^.*:/, ""); } From c4125a8334d0fb4c7737a96d928f745fa9360b0a Mon Sep 17 00:00:00 2001 From: theS1LV3R Date: Mon, 4 Jul 2022 20:38:44 +0200 Subject: [PATCH 3/5] style: fix linter error --- server/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.js b/server/server.js index 476f8664..d3ae075d 100644 --- a/server/server.js +++ b/server/server.js @@ -1677,7 +1677,7 @@ async function shutdownFunction(signal) { } function getClientIp(socket) { - return socket.client.conn.request.headers["x-forwarded-for"] + return socket.client.conn.request.headers["x-forwarded-for"] || socket.client.conn.request.headers["x-real-ip"] || socket.client.conn.remoteAddress.replace(/^.*:/, ""); } From a3b16129381b0e90a6914ce774ac47c05e976581 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sun, 31 Jul 2022 23:36:33 +0800 Subject: [PATCH 4/5] getClientIP respect trustProxy setting --- server/server.js | 44 +++++++++++++++++++----------------- server/uptime-kuma-server.js | 13 +++++++++++ 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/server/server.js b/server/server.js index 8a0dd871..61bd9d93 100644 --- a/server/server.js +++ b/server/server.js @@ -254,7 +254,9 @@ let needSetup = false; // *************************** socket.on("loginByToken", async (token, callback) => { - log.info("auth", `Login by token. IP=${getClientIp(socket)}`); + const clientIP = await server.getClientIP(socket); + + log.info("auth", `Login by token. IP=${clientIP}`); try { let decoded = jwt.verify(token, jwtSecret); @@ -270,14 +272,14 @@ let needSetup = false; afterLogin(socket, user); log.debug("auth", "afterLogin ok"); - log.info("auth", `Successfully logged in user ${decoded.username}. IP=${getClientIp(socket)}`); + log.info("auth", `Successfully logged in user ${decoded.username}. IP=${clientIP}`); callback({ ok: true, }); } else { - log.info("auth", `Inactive or deleted user ${decoded.username}. IP=${getClientIp(socket)}`); + log.info("auth", `Inactive or deleted user ${decoded.username}. IP=${clientIP}`); callback({ ok: false, @@ -286,7 +288,7 @@ let needSetup = false; } } catch (error) { - log.error("auth", `Invalid token. IP=${getClientIp(socket)}`); + log.error("auth", `Invalid token. IP=${clientIP}`); callback({ ok: false, @@ -297,7 +299,9 @@ let needSetup = false; }); socket.on("login", async (data, callback) => { - log.info("auth", `Login by username + password. IP=${getClientIp(socket)}`); + const clientIP = await server.getClientIP(socket); + + log.info("auth", `Login by username + password. IP=${clientIP}`); // Checking if (typeof callback !== "function") { @@ -310,7 +314,7 @@ let needSetup = false; // Login Rate Limit if (! await loginRateLimiter.pass(callback)) { - log.info("auth", `Too many failed requests for user ${data.username}. IP=${getClientIp(socket)}`); + log.info("auth", `Too many failed requests for user ${data.username}. IP=${clientIP}`); return; } @@ -320,7 +324,7 @@ let needSetup = false; if (user.twofa_status === 0) { afterLogin(socket, user); - log.info("auth", `Successfully logged in user ${data.username}. IP=${getClientIp(socket)}`); + log.info("auth", `Successfully logged in user ${data.username}. IP=${clientIP}`); callback({ ok: true, @@ -332,7 +336,7 @@ let needSetup = false; if (user.twofa_status === 1 && !data.token) { - log.info("auth", `2FA token required for user ${data.username}. IP=${getClientIp(socket)}`); + log.info("auth", `2FA token required for user ${data.username}. IP=${clientIP}`); callback({ tokenRequired: true, @@ -350,7 +354,7 @@ let needSetup = false; socket.userID, ]); - log.info("auth", `Successfully logged in user ${data.username}. IP=${getClientIp(socket)}`); + log.info("auth", `Successfully logged in user ${data.username}. IP=${clientIP}`); callback({ ok: true, @@ -360,7 +364,7 @@ let needSetup = false; }); } else { - log.warn("auth", `Invalid token provided for user ${data.username}. IP=${getClientIp(socket)}`); + log.warn("auth", `Invalid token provided for user ${data.username}. IP=${clientIP}`); callback({ ok: false, @@ -370,7 +374,7 @@ let needSetup = false; } } else { - log.warn("auth", `Incorrect username or password for user ${data.username}. IP=${getClientIp(socket)}`); + log.warn("auth", `Incorrect username or password for user ${data.username}. IP=${clientIP}`); callback({ ok: false, @@ -442,6 +446,8 @@ let needSetup = false; }); socket.on("save2FA", async (currentPassword, callback) => { + const clientIP = await server.getClientIP(socket); + try { if (! await twoFaRateLimiter.pass(callback)) { return; @@ -454,7 +460,7 @@ let needSetup = false; socket.userID, ]); - log.info("auth", `Saved 2FA token. IP=${getClientIp(socket)}`); + log.info("auth", `Saved 2FA token. IP=${clientIP}`); callback({ ok: true, @@ -462,7 +468,7 @@ let needSetup = false; }); } catch (error) { - log.error("auth", `Error changing 2FA token. IP=${getClientIp(socket)}`); + log.error("auth", `Error changing 2FA token. IP=${clientIP}`); callback({ ok: false, @@ -472,6 +478,8 @@ let needSetup = false; }); socket.on("disable2FA", async (currentPassword, callback) => { + const clientIP = await server.getClientIP(socket); + try { if (! await twoFaRateLimiter.pass(callback)) { return; @@ -481,7 +489,7 @@ let needSetup = false; await doubleCheckPassword(socket, currentPassword); await TwoFA.disable2FA(socket.userID); - log.info("auth", `Disabled 2FA token. IP=${getClientIp(socket)}`); + log.info("auth", `Disabled 2FA token. IP=${clientIP}`); callback({ ok: true, @@ -489,7 +497,7 @@ let needSetup = false; }); } catch (error) { - log.error("auth", `Error disabling 2FA token. IP=${getClientIp(socket)}`); + log.error("auth", `Error disabling 2FA token. IP=${clientIP}`); callback({ ok: false, @@ -1684,12 +1692,6 @@ async function shutdownFunction(signal) { await cloudflaredStop(); } -function getClientIp(socket) { - return socket.client.conn.request.headers["x-forwarded-for"] - || socket.client.conn.request.headers["x-real-ip"] - || socket.client.conn.remoteAddress.replace(/^.*:/, ""); -} - /** Final function called before application exits */ function finalFunction() { log.info("server", "Graceful shutdown successful!"); diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js index 3362f72c..0f32017f 100644 --- a/server/uptime-kuma-server.js +++ b/server/uptime-kuma-server.js @@ -8,6 +8,7 @@ const { log } = require("../src/util"); const Database = require("./database"); const util = require("util"); const { CacheableDnsHttpAgent } = require("./cacheable-dns-http-agent"); +const { setting } = require("./util-server"); /** * `module.exports` (alias: `server`) should be inside this class, in order to avoid circular dependency issue. @@ -128,6 +129,18 @@ class UptimeKumaServer { errorLogStream.end(); } + + async getClientIP(socket) { + const clientIP = socket.client.conn.remoteAddress.replace(/^.*:/, ""); + + if (await setting("trustProxy")) { + return socket.client.conn.request.headers["x-forwarded-for"] + || socket.client.conn.request.headers["x-real-ip"] + || clientIP; + } else { + return clientIP; + } + } } module.exports = { From 2389b604fe609cbc650d2761a52278cac0173391 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sun, 31 Jul 2022 23:41:29 +0800 Subject: [PATCH 5/5] Use Settings.get --- server/uptime-kuma-server.js | 4 ++-- server/util-server.js | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js index 0f32017f..67b6ed0a 100644 --- a/server/uptime-kuma-server.js +++ b/server/uptime-kuma-server.js @@ -8,7 +8,7 @@ const { log } = require("../src/util"); const Database = require("./database"); const util = require("util"); const { CacheableDnsHttpAgent } = require("./cacheable-dns-http-agent"); -const { setting } = require("./util-server"); +const { Settings } = require("./settings"); /** * `module.exports` (alias: `server`) should be inside this class, in order to avoid circular dependency issue. @@ -133,7 +133,7 @@ class UptimeKumaServer { async getClientIP(socket) { const clientIP = socket.client.conn.remoteAddress.replace(/^.*:/, ""); - if (await setting("trustProxy")) { + if (await Settings.get("trustProxy")) { return socket.client.conn.request.headers["x-forwarded-for"] || socket.client.conn.request.headers["x-real-ip"] || clientIP; diff --git a/server/util-server.js b/server/util-server.js index 84244b02..df711cf0 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -289,6 +289,7 @@ exports.postgresQuery = function (connectionString, query) { * Retrieve value of setting based on key * @param {string} key Key of setting to retrieve * @returns {Promise} Value + * @deprecated Use await Settings.get(key) */ exports.setting = async function (key) { return await Settings.get(key);