diff --git a/extra/reset-password.js b/extra/reset-password.js index be8c8e76..3b40e93e 100644 --- a/extra/reset-password.js +++ b/extra/reset-password.js @@ -99,7 +99,6 @@ function disconnectAllSocketClients(username, password) { // Disconnect all socket connections const socket = io(localWebSocketURL, { - transports: [ "websocket" ], reconnection: false, timeout: 5000, }); diff --git a/package.json b/package.json index 29289447..b73b015f 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "build-docker-nightly-local": "npm run build && docker build -f docker/dockerfile -t louislam/uptime-kuma:nightly2 --target nightly .", "build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test2 --target pr-test2 . --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", diff --git a/server/server.js b/server/server.js index 92077185..fdb3242e 100644 --- a/server/server.js +++ b/server/server.js @@ -53,7 +53,10 @@ if (!process.env.UPTIME_KUMA_WS_ORIGIN_CHECK) { log.info("server", "Env: " + process.env.NODE_ENV); log.debug("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); +} const checkVersion = require("./check-version"); log.info("server", "Uptime Kuma Version: " + checkVersion.version); diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js index 8e67ead7..2eec9c7c 100644 --- a/server/uptime-kuma-server.js +++ b/server/uptime-kuma-server.js @@ -114,39 +114,64 @@ class UptimeKumaServer { UptimeKumaServer.monitorTypeList["dns"] = new DnsMonitorType(); UptimeKumaServer.monitorTypeList["mqtt"] = new MqttMonitorType(); - this.io = new Server(this.httpServer, { - allowRequest: (req, callback) => { - let isOriginValid = true; - const bypass = isDev || process.env.UPTIME_KUMA_WS_ORIGIN_CHECK === "bypass"; + // Allow all CORS origins (polling) in development + let cors = undefined; + if (isDev) { + cors = { + origin: "*", + }; + } - if (!bypass) { - let host = req.headers.host; + this.io = new Server(this.httpServer, { + 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 this is set, it means the request is from the browser - let origin = req.headers.origin; + const clientIP = await this.getClientIPwithProxy(req.connection.remoteAddress, req.headers); + log.info("socket", `New ${transport} connection, IP = ${clientIP}`); + + // 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 { + let host = req.headers.host; + let origin = req.headers.origin; - // If this is from the browser, check if the origin is allowed - if (origin) { try { 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); } }); } @@ -290,20 +315,27 @@ class UptimeKumaServer { /** * Get the IP of the client connected to the socket * @param {Socket} socket Socket to query - * @returns {string} IP of client + * @returns {Promise} IP of client */ - 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 Raw client IP + * @param {IncomingHttpHeaders} headers HTTP headers + * @returns {Promise} Client IP with proxy (if trusted) + */ + 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 50b3dc49..0a8001a4 100644 --- a/src/mixins/socket.js +++ b/src/mixins/socket.js @@ -99,21 +99,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;