Merge branch '1.23.X' into version-merge

# Conflicts:
#	package-lock.json
#	package.json
#	server/server.js
#	server/uptime-kuma-server.js
This commit is contained in:
Louis Lam
2023-12-13 01:54:08 +08:00
5 changed files with 69 additions and 36 deletions

View File

@@ -114,39 +114,64 @@ class UptimeKumaServer {
UptimeKumaServer.monitorTypeList["dns"] = new DnsMonitorType();
UptimeKumaServer.monitorTypeList["mqtt"] = new MqttMonitorType();
// 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;
// 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<string>} 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<string>} 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:/, "");