From fa1214ae5ea61e0356e0664a396d21d17dd5b252 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Mon, 11 Dec 2023 19:30:01 +0800 Subject: [PATCH 1/4] Rebse #4213 (#4215) Co-authored-by: Nelson Chan --- server/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.js b/server/server.js index af37c949a..018218ea7 100644 --- a/server/server.js +++ b/server/server.js @@ -1151,7 +1151,7 @@ let needSetup = false; let user = await doubleCheckPassword(socket, password.currentPassword); await user.resetPassword(password.newPassword); - server.disconnectAllSocketClient(user.id, socket.id); + server.disconnectAllSocketClients(user.id, socket.id); callback({ ok: true, From f861a48dfc63614cc6993f29f78ec7bc42753144 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 12 Dec 2023 16:23:41 +0800 Subject: [PATCH 2/4] Smoothing the update for origin check (#4216) --- extra/reset-password.js | 1 - server/server.js | 5 ++- server/uptime-kuma-server.js | 84 +++++++++++++++++++++++++----------- src/mixins/socket.js | 15 +++---- 4 files changed, 69 insertions(+), 36 deletions(-) diff --git a/extra/reset-password.js b/extra/reset-password.js index 7f23a83f0..a7cf3e20b 100644 --- a/extra/reset-password.js +++ b/extra/reset-password.js @@ -78,7 +78,6 @@ function disconnectAllSocketClients(username, password) { // Disconnect all socket connections const socket = io(localWebSocketURL, { - transports: [ "websocket" ], reconnection: false, timeout: 5000, }); diff --git a/server/server.js b/server/server.js index 018218ea7..870986d5a 100644 --- a/server/server.js +++ b/server/server.js @@ -54,7 +54,10 @@ if (!process.env.UPTIME_KUMA_WS_ORIGIN_CHECK) { log.info("server", "Node Env: " + process.env.NODE_ENV); log.info("server", "Inside Container: " + (process.env.UPTIME_KUMA_IS_CONTAINER === "1")); -log.info("server", "WebSocket Origin Check: " + process.env.UPTIME_KUMA_WS_ORIGIN_CHECK); + +if (process.env.UPTIME_KUMA_WS_ORIGIN_CHECK === "bypass") { + log.warn("server", "WebSocket Origin Check: " + process.env.UPTIME_KUMA_WS_ORIGIN_CHECK); +} log.info("server", "Importing Node libraries"); const fs = require("fs"); diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js index 556f19d55..d20021909 100644 --- a/server/uptime-kuma-server.js +++ b/server/uptime-kuma-server.js @@ -99,39 +99,63 @@ class UptimeKumaServer { UptimeKumaServer.monitorTypeList["real-browser"] = new RealBrowserMonitorType(); UptimeKumaServer.monitorTypeList["tailscale-ping"] = new TailscalePing(); + // Allow all CORS origins (polling) in development + let cors = undefined; + if (isDev) { + cors = { + origin: "*", + }; + } + this.io = new Server(this.httpServer, { - allowRequest: (req, callback) => { - let isOriginValid = true; - const bypass = isDev || process.env.UPTIME_KUMA_WS_ORIGIN_CHECK === "bypass"; + cors, + allowRequest: async (req, callback) => { + let transport; + // It should be always true, but just in case, because this property is not documented + if (req._query) { + transport = req._query.transport; + } else { + log.error("socket", "Ops!!! Cannot get transport type, assume that it is polling"); + transport = "polling"; + } - if (!bypass) { - let host = req.headers.host; + const clientIP = await this.getClientIPwithProxy(req.connection.remoteAddress, req.headers); + log.info("socket", `New ${transport} connection, IP = ${clientIP}`); - // If this is set, it means the request is from the browser - let origin = req.headers.origin; - - // If this is from the browser, check if the origin is allowed - if (origin) { + // The following check is only for websocket connections, polling connections are already protected by CORS + if (transport === "polling") { + callback(null, true); + } else if (transport === "websocket") { + const bypass = process.env.UPTIME_KUMA_WS_ORIGIN_CHECK === "bypass"; + if (bypass) { + log.info("auth", "WebSocket origin check is bypassed"); + callback(null, true); + } else if (!req.headers.origin) { + log.info("auth", "WebSocket with no origin is allowed"); + callback(null, true); + } else { try { + let host = req.headers.host; + let origin = req.headers.origin; let originURL = new URL(origin); + let xForwardedFor; + if (await Settings.get("trustProxy")) { + xForwardedFor = req.headers["x-forwarded-for"]; + } - if (host !== originURL.host) { - isOriginValid = false; - log.error("auth", `Origin (${origin}) does not match host (${host}), IP: ${req.socket.remoteAddress}`); + if (host !== originURL.host && xForwardedFor !== originURL.host) { + callback(null, false); + log.error("auth", `Origin (${origin}) does not match host (${host}), IP: ${clientIP}`); + } else { + callback(null, true); } } catch (e) { // Invalid origin url, probably not from browser - isOriginValid = false; - log.error("auth", `Invalid origin url (${origin}), IP: ${req.socket.remoteAddress}`); + callback(null, false); + log.error("auth", `Invalid origin url (${origin}), IP: ${clientIP}`); } - } else { - log.info("auth", `Origin is not set, IP: ${req.socket.remoteAddress}`); } - } else { - log.debug("auth", "Origin check is bypassed"); } - - callback(null, isOriginValid); } }); } @@ -268,20 +292,28 @@ class UptimeKumaServer { /** * Get the IP of the client connected to the socket * @param {Socket} socket - * @returns {string} + * @returns {Promise} */ - async getClientIP(socket) { - let clientIP = socket.client.conn.remoteAddress; + getClientIP(socket) { + return this.getClientIPwithProxy(socket.client.conn.remoteAddress, socket.client.conn.request.headers); + } + /** + * + * @param {string} clientIP + * @param {IncomingHttpHeaders} headers + * @returns {Promise} + */ + async getClientIPwithProxy(clientIP, headers) { if (clientIP === undefined) { clientIP = ""; } if (await Settings.get("trustProxy")) { - const forwardedFor = socket.client.conn.request.headers["x-forwarded-for"]; + const forwardedFor = headers["x-forwarded-for"]; return (typeof forwardedFor === "string" ? forwardedFor.split(",")[0].trim() : null) - || socket.client.conn.request.headers["x-real-ip"] + || headers["x-real-ip"] || clientIP.replace(/^::ffff:/, ""); } else { return clientIP.replace(/^::ffff:/, ""); diff --git a/src/mixins/socket.js b/src/mixins/socket.js index 41c7fabd3..c48a008a3 100644 --- a/src/mixins/socket.js +++ b/src/mixins/socket.js @@ -91,21 +91,20 @@ export default { this.socket.initedSocketIO = true; - let protocol = (location.protocol === "https:") ? "wss://" : "ws://"; + let protocol = location.protocol + "//"; - let wsHost; + let url; const env = process.env.NODE_ENV || "production"; if (env === "development" && isDevContainer()) { - wsHost = protocol + getDevContainerServerHostname(); + url = protocol + getDevContainerServerHostname(); } else if (env === "development" || localStorage.dev === "dev") { - wsHost = protocol + location.hostname + ":3001"; + url = protocol + location.hostname + ":3001"; } else { - wsHost = protocol + location.host; + // Connect to the current url + url = undefined; } - socket = io(wsHost, { - transports: [ "websocket" ], - }); + socket = io(url); socket.on("info", (info) => { this.info = info; From 4245ea86e7aa09842bedd69e6a5772b34826261d Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Wed, 13 Dec 2023 00:55:58 +0800 Subject: [PATCH 3/4] Update to 1.23.10 --- package-lock.json | 4 ++-- package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index c5b3e1bbc..2b6c87455 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "uptime-kuma", - "version": "1.23.9", + "version": "1.23.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "uptime-kuma", - "version": "1.23.9", + "version": "1.23.10", "license": "MIT", "dependencies": { "@grpc/grpc-js": "~1.7.3", diff --git a/package.json b/package.json index 24cafdace..5f22a08e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uptime-kuma", - "version": "1.23.9", + "version": "1.23.10", "license": "MIT", "repository": { "type": "git", @@ -42,7 +42,7 @@ "build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain", "build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test --target pr-test . --push", "upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain", - "setup": "git checkout 1.23.9 && npm ci --production && npm run download-dist", + "setup": "git checkout 1.23.10 && npm ci --production && npm run download-dist", "download-dist": "node extra/download-dist.js", "mark-as-nightly": "node extra/mark-as-nightly.js", "reset-password": "node extra/reset-password.js", From 4185ec20b0dd0857973a1348756c8c50cdc7d711 Mon Sep 17 00:00:00 2001 From: Nelson Chan <3271800+chakflying@users.noreply.github.com> Date: Wed, 13 Dec 2023 01:35:39 +0800 Subject: [PATCH 4/4] Fix: Origin undefined on error handling (#4224) --- server/uptime-kuma-server.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js index d20021909..5742b29ac 100644 --- a/server/uptime-kuma-server.js +++ b/server/uptime-kuma-server.js @@ -134,9 +134,10 @@ class UptimeKumaServer { log.info("auth", "WebSocket with no origin is allowed"); callback(null, true); } else { + let host = req.headers.host; + let origin = req.headers.origin; + try { - let host = req.headers.host; - let origin = req.headers.origin; let originURL = new URL(origin); let xForwardedFor; if (await Settings.get("trustProxy")) {