mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-09-11 13:36:55 +08:00
Compare commits
26 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
e797abd108 | ||
|
7a9e2f5de6 | ||
|
7b5d2a71ff | ||
|
893278bd3d | ||
|
0e30ea830d | ||
|
c67a2070b8 | ||
|
9863a10321 | ||
|
ee7f8680c1 | ||
|
c1301804d4 | ||
|
b385e81608 | ||
|
f37f55e06c | ||
|
87d7a780e3 | ||
|
0fc372f558 | ||
|
67a13e1259 | ||
|
2b8f55194f | ||
|
288cab6dd7 | ||
|
b4e45c7ce8 | ||
|
7635ab54a0 | ||
|
458cdf9f9b | ||
|
f1e2ee74ea | ||
|
8d847abf35 | ||
|
8151ac0e25 | ||
|
4185ec20b0 | ||
|
4245ea86e7 | ||
|
f861a48dfc | ||
|
fa1214ae5e |
26
.github/workflows/auto-test.yml
vendored
26
.github/workflows/auto-test.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest, ARM64]
|
||||
node: [ 14, 20 ]
|
||||
node: [ 16, 20.5 ]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
@@ -33,8 +33,7 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- run: npm install npm@9 -g
|
||||
- run: npm install
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- run: npm test
|
||||
env:
|
||||
@@ -50,7 +49,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ARMv7 ]
|
||||
node: [ 14, 20 ]
|
||||
node: [ 16, 20.5 ]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
@@ -61,7 +60,6 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- run: npm install npm@9 -g
|
||||
- run: npm ci --production
|
||||
|
||||
check-linters:
|
||||
@@ -71,11 +69,11 @@ jobs:
|
||||
- run: git config --global core.autocrlf false # Mainly for Windows
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js 14
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 14
|
||||
- run: npm install
|
||||
node-version: 20.5
|
||||
- run: npm ci
|
||||
- run: npm run lint:prod
|
||||
|
||||
e2e-tests:
|
||||
@@ -85,11 +83,11 @@ jobs:
|
||||
- run: git config --global core.autocrlf false # Mainly for Windows
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js 14
|
||||
- name: Use Node.js 16
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 14
|
||||
- run: npm install
|
||||
node-version: 16
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- run: npm run cy:test
|
||||
|
||||
@@ -100,10 +98,10 @@ jobs:
|
||||
- run: git config --global core.autocrlf false # Mainly for Windows
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js 14
|
||||
- name: Use Node.js 16
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 14
|
||||
- run: npm install
|
||||
node-version: 16
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- run: npm run cy:run:unit
|
||||
|
18
db/patch-monitor-tls-info-add-fk.sql
Normal file
18
db/patch-monitor-tls-info-add-fk.sql
Normal file
@@ -0,0 +1,18 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
PRAGMA writable_schema = TRUE;
|
||||
|
||||
UPDATE
|
||||
SQLITE_MASTER
|
||||
SET
|
||||
sql = replace(sql,
|
||||
'monitor_id INTEGER NOT NULL',
|
||||
'monitor_id INTEGER NOT NULL REFERENCES [monitor] ([id]) ON DELETE CASCADE ON UPDATE CASCADE'
|
||||
)
|
||||
WHERE
|
||||
name = 'monitor_tls_info'
|
||||
AND type = 'table';
|
||||
|
||||
PRAGMA writable_schema = RESET;
|
||||
|
||||
COMMIT;
|
@@ -78,7 +78,6 @@ function disconnectAllSocketClients(username, password) {
|
||||
|
||||
// Disconnect all socket connections
|
||||
const socket = io(localWebSocketURL, {
|
||||
transports: [ "websocket" ],
|
||||
reconnection: false,
|
||||
timeout: 5000,
|
||||
});
|
||||
|
4466
package-lock.json
generated
4466
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
20
package.json
20
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "uptime-kuma",
|
||||
"version": "1.23.9",
|
||||
"version": "1.23.12",
|
||||
"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.12 && 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",
|
||||
@@ -82,7 +82,7 @@
|
||||
"@louislam/ping": "~0.4.4-mod.1",
|
||||
"@louislam/sqlite3": "15.1.6",
|
||||
"args-parser": "~1.3.0",
|
||||
"axios": "~0.27.0",
|
||||
"axios": "~0.28.1",
|
||||
"axios-ntlm": "1.3.0",
|
||||
"badge-maker": "~3.3.1",
|
||||
"bcryptjs": "~2.4.3",
|
||||
@@ -97,7 +97,7 @@
|
||||
"croner": "~6.0.5",
|
||||
"dayjs": "~1.11.5",
|
||||
"dotenv": "~16.0.3",
|
||||
"express": "~4.17.3",
|
||||
"express": "~4.19.2",
|
||||
"express-basic-auth": "~1.2.1",
|
||||
"express-static-gzip": "~2.1.7",
|
||||
"form-data": "~4.0.0",
|
||||
@@ -118,11 +118,11 @@
|
||||
"mongodb": "~4.17.1",
|
||||
"mqtt": "~4.3.7",
|
||||
"mssql": "~8.1.4",
|
||||
"mysql2": "~3.6.2",
|
||||
"mysql2": "~3.9.6",
|
||||
"nanoid": "~3.3.4",
|
||||
"node-cloudflared-tunnel": "~1.0.9",
|
||||
"node-radius-client": "~1.0.0",
|
||||
"nodemailer": "~6.6.5",
|
||||
"nodemailer": "~6.9.13",
|
||||
"nostr-tools": "^1.13.1",
|
||||
"notp": "~2.0.3",
|
||||
"openid-client": "^5.4.2",
|
||||
@@ -141,13 +141,13 @@
|
||||
"socket.io": "~4.6.1",
|
||||
"socket.io-client": "~4.6.1",
|
||||
"socks-proxy-agent": "6.1.1",
|
||||
"tar": "~6.1.11",
|
||||
"tar": "~6.2.1",
|
||||
"tcp-ping": "~0.1.1",
|
||||
"thirty-two": "~1.0.2",
|
||||
"ws": "^8.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/github": "~5.0.1",
|
||||
"@actions/github": "~5.1.1",
|
||||
"@babel/eslint-parser": "^7.22.7",
|
||||
"@babel/preset-env": "^7.15.8",
|
||||
"@fortawesome/fontawesome-svg-core": "~1.2.36",
|
||||
@@ -171,7 +171,7 @@
|
||||
"cypress": "^13.2.0",
|
||||
"delay": "^5.0.0",
|
||||
"dns2": "~2.0.1",
|
||||
"dompurify": "~2.4.3",
|
||||
"dompurify": "~3.0.11",
|
||||
"eslint": "~8.14.0",
|
||||
"eslint-plugin-vue": "~8.7.1",
|
||||
"favico.js": "~0.3.10",
|
||||
@@ -191,7 +191,7 @@
|
||||
"timezones-list": "~3.0.1",
|
||||
"typescript": "~4.4.4",
|
||||
"v-pagination-3": "~0.1.7",
|
||||
"vite": "~4.4.1",
|
||||
"vite": "~5.2.8",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vue": "~3.3.4",
|
||||
"vue-chartjs": "~5.2.0",
|
||||
|
@@ -84,6 +84,7 @@ class Database {
|
||||
"patch-notification-config.sql": true,
|
||||
"patch-fix-kafka-producer-booleans.sql": true,
|
||||
"patch-timeout.sql": true,
|
||||
"patch-monitor-tls-info-add-fk.sql": true,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@@ -230,10 +230,12 @@ class Monitor extends BeanModel {
|
||||
/**
|
||||
* Encode user and password to Base64 encoding
|
||||
* for HTTP "basic" auth, as per RFC-7617
|
||||
* @param {string|null} user - The username (nullable if not changed by a user)
|
||||
* @param {string|null} pass - The password (nullable if not changed by a user)
|
||||
* @returns {string}
|
||||
*/
|
||||
encodeBase64(user, pass) {
|
||||
return Buffer.from(user + ":" + pass).toString("base64");
|
||||
return Buffer.from(`${user || ""}:${pass || ""}`).toString("base64");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -510,6 +512,12 @@ class Monitor extends BeanModel {
|
||||
}
|
||||
}
|
||||
|
||||
let tlsInfo;
|
||||
// Store tlsInfo when key material is received
|
||||
options.httpsAgent.on("keylog", (line, tlsSocket) => {
|
||||
tlsInfo = checkCertificate(tlsSocket);
|
||||
});
|
||||
|
||||
log.debug("monitor", `[${this.name}] Axios Options: ${JSON.stringify(options)}`);
|
||||
log.debug("monitor", `[${this.name}] Axios Request`);
|
||||
|
||||
@@ -519,29 +527,20 @@ class Monitor extends BeanModel {
|
||||
bean.msg = `${res.status} - ${res.statusText}`;
|
||||
bean.ping = dayjs().valueOf() - startTime;
|
||||
|
||||
// Check certificate if https is used
|
||||
let certInfoStartTime = dayjs().valueOf();
|
||||
// Store certificate and check for expiry if https is used
|
||||
if (this.getUrl()?.protocol === "https:") {
|
||||
log.debug("monitor", `[${this.name}] Check cert`);
|
||||
try {
|
||||
let tlsInfoObject = checkCertificate(res);
|
||||
tlsInfo = await this.updateTlsInfo(tlsInfoObject);
|
||||
// No way to listen for the `secureConnection` event, so we do it here
|
||||
const tlssocket = res.request.res.socket;
|
||||
|
||||
if (!this.getIgnoreTls() && this.isEnabledExpiryNotification()) {
|
||||
log.debug("monitor", `[${this.name}] call checkCertExpiryNotifications`);
|
||||
await this.checkCertExpiryNotifications(tlsInfoObject);
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
if (e.message !== "No TLS certificate in response") {
|
||||
log.error("monitor", "Caught error");
|
||||
log.error("monitor", e.message);
|
||||
}
|
||||
if (tlssocket) {
|
||||
tlsInfo.valid = tlssocket.authorized || false;
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.TIMELOGGER === "1") {
|
||||
log.debug("monitor", "Cert Info Query Time: " + (dayjs().valueOf() - certInfoStartTime) + "ms");
|
||||
await this.updateTlsInfo(tlsInfo);
|
||||
if (!this.getIgnoreTls() && this.isEnabledExpiryNotification()) {
|
||||
log.debug("monitor", `[${this.name}] call checkCertExpiryNotifications`);
|
||||
await this.checkCertExpiryNotifications(tlsInfo);
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.UPTIME_KUMA_LOG_RESPONSE_BODY_MONITOR_ID === this.id) {
|
||||
@@ -576,8 +575,12 @@ class Monitor extends BeanModel {
|
||||
let data = res.data;
|
||||
|
||||
// convert data to object
|
||||
if (typeof data === "string") {
|
||||
data = JSON.parse(data);
|
||||
if (typeof data === "string" && res.headers["content-type"] !== "application/json") {
|
||||
try {
|
||||
data = JSON.parse(data);
|
||||
} catch (_) {
|
||||
// Failed to parse as JSON, just process it as a string
|
||||
}
|
||||
}
|
||||
|
||||
let expression = jsonata(this.jsonPath);
|
||||
|
@@ -9,6 +9,10 @@ const Database = require("../database");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const config = require("../config");
|
||||
|
||||
/**
|
||||
* Cached instance of a browser
|
||||
* @type {import ("playwright-core").Browser}
|
||||
*/
|
||||
let browser = null;
|
||||
|
||||
let allowedList = [];
|
||||
@@ -62,8 +66,15 @@ async function isAllowedChromeExecutable(executablePath) {
|
||||
return allowedList.includes(executablePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current instance of the browser. If there isn't one, create
|
||||
* it.
|
||||
* @returns {Promise<import ("playwright-core").Browser>} The browser
|
||||
*/
|
||||
async function getBrowser() {
|
||||
if (!browser) {
|
||||
if (browser && browser.isConnected()) {
|
||||
return browser;
|
||||
} else {
|
||||
let executablePath = await Settings.get("chromeExecutable");
|
||||
|
||||
executablePath = await prepareChromeExecutable(executablePath);
|
||||
@@ -72,8 +83,9 @@ async function getBrowser() {
|
||||
//headless: false,
|
||||
executablePath,
|
||||
});
|
||||
|
||||
return browser;
|
||||
}
|
||||
return browser;
|
||||
}
|
||||
|
||||
async function prepareChromeExecutable(executablePath) {
|
||||
|
@@ -39,7 +39,8 @@ class TailscalePing extends MonitorType {
|
||||
async runTailscalePing(hostname, interval) {
|
||||
let timeout = interval * 1000 * 0.8;
|
||||
let res = await childProcessAsync.spawn("tailscale", [ "ping", "--c", "1", hostname ], {
|
||||
timeout: timeout
|
||||
timeout: timeout,
|
||||
encoding: "utf8",
|
||||
});
|
||||
if (res.stderr && res.stderr.toString()) {
|
||||
throw new Error(`Error in output: ${res.stderr.toString()}`);
|
||||
|
@@ -11,7 +11,9 @@ class Apprise extends NotificationProvider {
|
||||
args.push("-t");
|
||||
args.push(notification.title);
|
||||
}
|
||||
const s = await childProcessAsync.spawn("apprise", args);
|
||||
const s = await childProcessAsync.spawn("apprise", args, {
|
||||
encoding: "utf8",
|
||||
});
|
||||
|
||||
const output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found";
|
||||
|
||||
|
@@ -18,7 +18,7 @@ class DingDing extends NotificationProvider {
|
||||
text: `## [${this.statusToString(heartbeatJSON["status"])}] ${monitorJSON["name"]} \n> ${heartbeatJSON["msg"]}\n> Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`,
|
||||
}
|
||||
};
|
||||
if (this.sendToDingDing(notification, params)) {
|
||||
if (await this.sendToDingDing(notification, params)) {
|
||||
return okMsg;
|
||||
}
|
||||
} else {
|
||||
@@ -28,7 +28,7 @@ class DingDing extends NotificationProvider {
|
||||
content: msg
|
||||
}
|
||||
};
|
||||
if (this.sendToDingDing(notification, params)) {
|
||||
if (await this.sendToDingDing(notification, params)) {
|
||||
return okMsg;
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ class DingDing extends NotificationProvider {
|
||||
if (result.data.errmsg === "ok") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
throw new Error(result.data.errmsg);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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");
|
||||
@@ -1151,7 +1154,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,
|
||||
@@ -1202,6 +1205,12 @@ let needSetup = false;
|
||||
await doubleCheckPassword(socket, currentPassword);
|
||||
}
|
||||
|
||||
// Log out all clients if enabling auth
|
||||
// GHSA-23q2-5gf8-gjpp
|
||||
if (currentDisabledAuth && !data.disableAuth) {
|
||||
server.disconnectAllSocketClients(socket.userID, socket.id);
|
||||
}
|
||||
|
||||
const previousChromeExecutable = await Settings.get("chromeExecutable");
|
||||
const previousNSCDStatus = await Settings.get("nscd");
|
||||
|
||||
|
@@ -99,39 +99,64 @@ 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;
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -268,20 +293,28 @@ class UptimeKumaServer {
|
||||
/**
|
||||
* Get the IP of the client connected to the socket
|
||||
* @param {Socket} socket
|
||||
* @returns {string}
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
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<string>}
|
||||
*/
|
||||
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:/, "");
|
||||
|
@@ -716,20 +716,27 @@ const parseCertificateInfo = function (info) {
|
||||
|
||||
/**
|
||||
* Check if certificate is valid
|
||||
* @param {Object} res Response object from axios
|
||||
* @param {tls.TLSSocket} socket TLSSocket, which may or may not be connected
|
||||
* @returns {Object} Object containing certificate information
|
||||
*/
|
||||
exports.checkCertificate = function (res) {
|
||||
if (!res.request.res.socket) {
|
||||
throw new Error("No socket found");
|
||||
exports.checkCertificate = function (socket) {
|
||||
let certInfoStartTime = dayjs().valueOf();
|
||||
|
||||
// Return null if there is no socket
|
||||
if (socket === undefined || socket == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const info = res.request.res.socket.getPeerCertificate(true);
|
||||
const valid = res.request.res.socket.authorized || false;
|
||||
const info = socket.getPeerCertificate(true);
|
||||
const valid = socket.authorized || false;
|
||||
|
||||
log.debug("cert", "Parsing Certificate Info");
|
||||
const parsedInfo = parseCertificateInfo(info);
|
||||
|
||||
if (process.env.TIMELOGGER === "1") {
|
||||
log.debug("monitor", "Cert Info Query Time: " + (dayjs().valueOf() - certInfoStartTime) + "ms");
|
||||
}
|
||||
|
||||
return {
|
||||
valid: valid,
|
||||
certInfo: parsedInfo
|
||||
|
@@ -5,6 +5,14 @@
|
||||
<input id="hostname" v-model="$parent.notification.smtpHost" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<i18n-t tag="div" keypath="Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent" class="form-text">
|
||||
<template #localhost>
|
||||
<code>localhost</code>
|
||||
</template>
|
||||
<template #local_mta>
|
||||
<a href="https://wikipedia.org/wiki/Mail_Transfer_Agent" target="_blank">{{ $t("locally configured mail transfer agent") }}</a>
|
||||
</template>
|
||||
</i18n-t>
|
||||
<div class="mb-3">
|
||||
<label for="port" class="form-label">{{ $t("Port") }}</label>
|
||||
<input id="port" v-model="$parent.notification.smtpPort" type="number" class="form-control" required min="0" max="65535" step="1">
|
||||
|
14
src/i18n.js
14
src/i18n.js
@@ -57,10 +57,16 @@ for (let lang in languageList) {
|
||||
|
||||
const rtlLangs = [ "fa", "ar-SY", "ur" ];
|
||||
|
||||
export const currentLocale = () => localStorage.locale
|
||||
|| languageList[navigator.language] && navigator.language
|
||||
|| languageList[navigator.language.substring(0, 2)] && navigator.language.substring(0, 2)
|
||||
|| "en";
|
||||
/**
|
||||
* Find the best matching locale to display
|
||||
* If no locale can be matched, the default is "en"
|
||||
* @returns {string} the locale that should be displayed
|
||||
*/
|
||||
export function currentLocale() {
|
||||
const potentialLocales = [ localStorage.locale, navigator.language, navigator.language.substring(0, 2), ...navigator.languages ];
|
||||
const availableLocales = potentialLocales.filter(l => languageList[l]);
|
||||
return availableLocales[0] || "en";
|
||||
}
|
||||
|
||||
export const localeDirection = () => {
|
||||
return rtlLangs.includes(currentLocale()) ? "rtl" : "ltr";
|
||||
|
@@ -57,6 +57,8 @@
|
||||
"Friendly Name": "Friendly Name",
|
||||
"URL": "URL",
|
||||
"Hostname": "Hostname",
|
||||
"locally configured mail transfer agent": "locally configured mail transfer agent",
|
||||
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "Either enter the hostname of the server you want to connect to or {localhost} if you intend to use a {local_mta}",
|
||||
"Port": "Port",
|
||||
"Heartbeat Interval": "Heartbeat Interval",
|
||||
"Request Timeout": "Request Timeout",
|
||||
|
@@ -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;
|
||||
|
@@ -4,9 +4,13 @@ describe("Test i18n.js", () => {
|
||||
|
||||
it("currentLocale()", () => {
|
||||
const setLanguage = (language) => {
|
||||
Object.defineProperty(window.navigator, 'language', {
|
||||
value: language,
|
||||
writable: true
|
||||
Object.defineProperty(window.navigator, 'language', {
|
||||
value: language,
|
||||
writable: true
|
||||
});
|
||||
Object.defineProperty(window.navigator, 'languages', {
|
||||
value: [language],
|
||||
writable: true
|
||||
});
|
||||
}
|
||||
setLanguage('en-EN');
|
||||
@@ -41,4 +45,4 @@ describe("Test i18n.js", () => {
|
||||
expect(currentLocale()).equal("zh-HK");
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user