diff --git a/package-lock.json b/package-lock.json index 718270427..4f2221714 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "uptime-kuma", - "version": "1.18.2", + "version": "1.18.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "uptime-kuma", - "version": "1.18.2", + "version": "1.18.3", "license": "MIT", "dependencies": { "@louislam/sqlite3": "~15.0.6", @@ -33,6 +33,7 @@ "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "iconv-lite": "^0.6.3", + "jsesc": "^3.0.2", "jsonwebtoken": "~8.5.1", "jwt-decode": "^3.1.2", "limiter": "^2.1.0", @@ -474,6 +475,18 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/generator/node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/helper-annotate-as-pure": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", @@ -11229,15 +11242,14 @@ "dev": true }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-parse-even-better-errors": { @@ -16957,6 +16969,14 @@ "@babel/types": "^7.18.13", "@jridgewell/gen-mapping": "^0.3.2", "jsesc": "^2.5.1" + }, + "dependencies": { + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + } } }, "@babel/helper-annotate-as-pure": { @@ -25010,10 +25030,9 @@ } }, "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==" }, "json-parse-even-better-errors": { "version": "2.3.1", diff --git a/package.json b/package.json index 4f7da6815..d478a1d24 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uptime-kuma", - "version": "1.18.2", + "version": "1.18.3", "license": "MIT", "repository": { "type": "git", @@ -23,9 +23,9 @@ "start-server": "node server/server.js", "start-server-dev": "cross-env NODE_ENV=development node server/server.js", "build": "vite build --config ./config/vite.config.js", - "test": "node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/ --test", + "test": "node test/prepare-test-server.js && npm run jest-backend", "test-with-build": "npm run build && npm test", - "jest-backend": "cross-env TEST_BACKEND=1 jest --config=./config/jest-backend.config.js", + "jest-backend": "cross-env TEST_BACKEND=1 jest --runInBand --detectOpenHandles --forceExit --config=./config/jest-backend.config.js", "tsc": "tsc", "vite-preview-dist": "vite preview --host --config ./config/vite.config.js", "build-docker": "npm run build && npm run build-docker-debian && npm run build-docker-alpine", @@ -38,7 +38,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.18.2 && npm ci --production && npm run download-dist", + "setup": "git checkout 1.18.3 && 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", @@ -88,6 +88,7 @@ "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "iconv-lite": "^0.6.3", + "jsesc": "^3.0.2", "jsonwebtoken": "~8.5.1", "jwt-decode": "^3.1.2", "limiter": "^2.1.0", diff --git a/server/model/status_page.js b/server/model/status_page.js index 82d184bfd..7682272c9 100644 --- a/server/model/status_page.js +++ b/server/model/status_page.js @@ -2,6 +2,7 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); const { R } = require("redbean-node"); const cheerio = require("cheerio"); const { UptimeKumaServer } = require("../uptime-kuma-server"); +const jsesc = require("jsesc"); class StatusPage extends BeanModel { @@ -56,13 +57,19 @@ class StatusPage extends BeanModel { head.append(``); // Preload data - const json = JSON.stringify(await StatusPage.getStatusPageData(statusPage)); - head.append(` - `); + head.append(script); + // manifest.json $("link[rel=manifest]").attr("href", `/api/status-page/${statusPage.slug}/manifest.json`); diff --git a/server/notification-providers/freemobile.js b/server/notification-providers/freemobile.js new file mode 100644 index 000000000..919150fa0 --- /dev/null +++ b/server/notification-providers/freemobile.js @@ -0,0 +1,24 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +class FreeMobile extends NotificationProvider { + + name = "FreeMobile"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully."; + try { + await axios.post(`https://smsapi.free-mobile.fr/sendmsg?msg=${encodeURIComponent(msg.replace("🔴", "⛔️"))}`, { + "user": notification.freemobileUser, + "pass": notification.freemobilePass, + }); + + return okMsg; + + } catch (error) { + this.throwGeneralAxiosError(error); + } + } +} + +module.exports = FreeMobile; diff --git a/server/notification-providers/ntfy.js b/server/notification-providers/ntfy.js index 17d6d812a..5381da466 100644 --- a/server/notification-providers/ntfy.js +++ b/server/notification-providers/ntfy.js @@ -9,7 +9,7 @@ class Ntfy extends NotificationProvider { let okMsg = "Sent Successfully."; try { let headers = {}; - if (notification.ntfyusername.length > 0) { + if (notification.ntfyusername) { headers = { "Authorization": "Basic " + Buffer.from(notification.ntfyusername + ":" + notification.ntfypassword).toString("base64"), }; diff --git a/server/notification.js b/server/notification.js index 7a4b4f29f..aed92e5dc 100644 --- a/server/notification.js +++ b/server/notification.js @@ -9,6 +9,7 @@ const ClickSendSMS = require("./notification-providers/clicksendsms"); const DingDing = require("./notification-providers/dingding"); const Discord = require("./notification-providers/discord"); const Feishu = require("./notification-providers/feishu"); +const FreeMobile = require("./notification-providers/freemobile"); const GoogleChat = require("./notification-providers/google-chat"); const Gorush = require("./notification-providers/gorush"); const Gotify = require("./notification-providers/gotify"); @@ -63,6 +64,7 @@ class Notification { new DingDing(), new Discord(), new Feishu(), + new FreeMobile(), new GoogleChat(), new Gorush(), new Gotify(), diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js index 98de65a43..6e77e1fd2 100644 --- a/server/uptime-kuma-server.js +++ b/server/uptime-kuma-server.js @@ -138,7 +138,9 @@ class UptimeKumaServer { } if (await Settings.get("trustProxy")) { - return socket.client.conn.request.headers["x-forwarded-for"] + const forwardedFor = socket.client.conn.request.headers["x-forwarded-for"]; + + return (typeof forwardedFor === "string" ? forwardedFor.split(",")[0].trim() : null) || socket.client.conn.request.headers["x-real-ip"] || clientIP.replace(/^.*:/, ""); } else { diff --git a/src/components/notifications/FreeMobile.vue b/src/components/notifications/FreeMobile.vue new file mode 100644 index 000000000..852d9ae2a --- /dev/null +++ b/src/components/notifications/FreeMobile.vue @@ -0,0 +1,12 @@ + + diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js index 319a7922e..bca4a510d 100644 --- a/src/components/notifications/index.js +++ b/src/components/notifications/index.js @@ -7,6 +7,7 @@ import ClickSendSMS from "./ClickSendSMS.vue"; import DingDing from "./DingDing.vue"; import Discord from "./Discord.vue"; import Feishu from "./Feishu.vue"; +import FreeMobile from "./FreeMobile.vue"; import GoogleChat from "./GoogleChat.vue"; import Gorush from "./Gorush.vue"; import Gotify from "./Gotify.vue"; @@ -56,6 +57,7 @@ const NotificationFormList = { "DingDing": DingDing, "discord": Discord, "Feishu": Feishu, + "FreeMobile": FreeMobile, "GoogleChat": GoogleChat, "gorush": Gorush, "gotify": Gotify, diff --git a/test/backend.spec.js b/test/backend.spec.js index 6deb28534..5b9fa92c6 100644 --- a/test/backend.spec.js +++ b/test/backend.spec.js @@ -1,7 +1,11 @@ -const { genSecret, DOWN } = require("../src/util"); +const { genSecret, DOWN, log} = require("../src/util"); const utilServerRewire = require("../server/util-server"); const Discord = require("../server/notification-providers/discord"); const axios = require("axios"); +const { UptimeKumaServer } = require("../server/uptime-kuma-server"); +const Database = require("../server/database"); +const {Settings} = require("../server/settings"); +const fs = require("fs"); jest.mock("axios"); @@ -225,3 +229,80 @@ describe("The function filterAndJoin", () => { expect(result).toBe(""); }); }); + +describe("Test uptimeKumaServer.getClientIP()", () => { + it("should able to get a correct client IP", async () => { + Database.init({ + "data-dir": "./data/test" + }); + + if (! fs.existsSync(Database.path)) { + log.info("server", "Copying Database"); + fs.copyFileSync(Database.templatePath, Database.path); + } + + await Database.connect(true); + await Database.patch(); + + const fakeSocket = { + client: { + conn: { + remoteAddress: "192.168.10.10", + request: { + headers: { + } + } + } + } + } + const server = Object.create(UptimeKumaServer.prototype); + let ip = await server.getClientIP(fakeSocket); + + await Settings.set("trustProxy", false); + expect(await Settings.get("trustProxy")).toBe(false); + expect(ip).toBe("192.168.10.10"); + + fakeSocket.client.conn.request.headers["x-forwarded-for"] = "10.10.10.10"; + ip = await server.getClientIP(fakeSocket); + expect(ip).toBe("192.168.10.10"); + + fakeSocket.client.conn.request.headers["x-real-ip"] = "20.20.20.20"; + ip = await server.getClientIP(fakeSocket); + expect(ip).toBe("192.168.10.10"); + + await Settings.set("trustProxy", true); + expect(await Settings.get("trustProxy")).toBe(true); + + fakeSocket.client.conn.request.headers["x-forwarded-for"] = "10.10.10.10"; + ip = await server.getClientIP(fakeSocket); + expect(ip).toBe("10.10.10.10"); + + // x-real-ip + delete fakeSocket.client.conn.request.headers["x-forwarded-for"]; + ip = await server.getClientIP(fakeSocket); + expect(ip).toBe("20.20.20.20"); + + fakeSocket.client.conn.request.headers["x-forwarded-for"] = "2001:db8:85a3:8d3:1319:8a2e:370:7348"; + ip = await server.getClientIP(fakeSocket); + expect(ip).toBe("2001:db8:85a3:8d3:1319:8a2e:370:7348"); + + fakeSocket.client.conn.request.headers["x-forwarded-for"] = "203.0.113.195"; + ip = await server.getClientIP(fakeSocket); + expect(ip).toBe("203.0.113.195"); + + fakeSocket.client.conn.request.headers["x-forwarded-for"] = "203.0.113.195, 2001:db8:85a3:8d3:1319:8a2e:370:7348"; + ip = await server.getClientIP(fakeSocket); + expect(ip).toBe("203.0.113.195"); + + fakeSocket.client.conn.request.headers["x-forwarded-for"] = "203.0.113.195,2001:db8:85a3:8d3:1319:8a2e:370:7348,150.172.238.178"; + ip = await server.getClientIP(fakeSocket); + expect(ip).toBe("203.0.113.195"); + + // Elements are comma-separated, with optional whitespace surrounding the commas. + fakeSocket.client.conn.request.headers["x-forwarded-for"] = "203.0.113.195 , 2001:db8:85a3:8d3:1319:8a2e:370:7348,150.172.238.178"; + ip = await server.getClientIP(fakeSocket); + expect(ip).toBe("203.0.113.195"); + + await Database.close(); + }, 120000); +});