Merge branch 'master' into 1.23.X-merge-to-2.X.X

# Conflicts:
#	docker/debian-base.dockerfile
#	package-lock.json
#	server/database.js
#	server/model/monitor.js
#	server/uptime-kuma-server.js
#	server/util-server.js
This commit is contained in:
Louis Lam 2023-11-13 21:15:51 +08:00
commit ace1fe00c2
316 changed files with 9487 additions and 4997 deletions

View file

@ -39,7 +39,7 @@ const crypto = require("crypto");
const isWindows = process.platform === /^win/.test(process.platform);
/**
* Init or reset JWT secret
* @returns {Promise<Bean>}
* @returns {Promise<Bean>} JWT secret
*/
exports.initJWTSecret = async () => {
let jwtSecretBean = await R.findOne("setting", " `key` = ? ", [
@ -59,7 +59,7 @@ exports.initJWTSecret = async () => {
/**
* Decodes a jwt and returns the payload portion without verifying the jqt.
* @param {string} jwt The input jwt as a string
* @returns {Object} Decoded jwt payload object
* @returns {object} Decoded jwt payload object
*/
exports.decodeJwt = (jwt) => {
return JSON.parse(Buffer.from(jwt.split(".")[1], "base64").toString());
@ -123,7 +123,7 @@ exports.tcping = function (hostname, port) {
/**
* Ping the specified machine
* @param {string} hostname Hostname / address of machine
* @param {number} [size=56] Size of packet to send
* @param {number} size Size of packet to send
* @returns {Promise<number>} Time for ping in ms rounded to nearest integer
*/
exports.ping = async (hostname, size = 56) => {
@ -146,7 +146,7 @@ exports.ping = async (hostname, size = 56) => {
* Ping the specified machine
* @param {string} hostname Hostname / address of machine to ping
* @param {boolean} ipv6 Should IPv6 be used?
* @param {number} [size = 56] Size of ping packet to send
* @param {number} size Size of ping packet to send
* @returns {Promise<number>} Time for ping in ms rounded to nearest integer
*/
exports.pingAsync = function (hostname, ipv6 = false, size = 56) {
@ -178,9 +178,9 @@ exports.pingAsync = function (hostname, ipv6 = false, size = 56) {
* @param {string} hostname Hostname / address of machine to test
* @param {string} topic MQTT topic
* @param {string} okMessage Expected result
* @param {Object} [options={}] MQTT options. Contains port, username,
* @param {object} options MQTT options. Contains port, username,
* password and interval (interval defaults to 20)
* @returns {Promise<string>}
* @returns {Promise<string>} Received MQTT message
*/
exports.mqttAsync = function (hostname, topic, okMessage, options = {}) {
return new Promise((resolve, reject) => {
@ -242,16 +242,17 @@ exports.mqttAsync = function (hostname, topic, okMessage, options = {}) {
/**
* Monitor Kafka using Producer
* @param {string[]} brokers List of kafka brokers to connect, host and
* port joined by ':'
* @param {string} topic Topic name to produce into
* @param {string} message Message to produce
* @param {Object} [options={interval = 20, allowAutoTopicCreation = false, ssl = false, clientId = "Uptime-Kuma"}]
* Kafka client options. Contains ssl, clientId, allowAutoTopicCreation and
* interval (interval defaults to 20, allowAutoTopicCreation defaults to false, clientId defaults to "Uptime-Kuma"
* and ssl defaults to false)
* @param {string[]} brokers List of kafka brokers to connect, host and port joined by ':'
* @param {SASLOptions} [saslOptions={}] Options for kafka client Authentication (SASL) (defaults to
* {})
* @returns {Promise<string>}
* @param {object} options Kafka client options. Contains ssl, clientId,
* allowAutoTopicCreation and interval (interval defaults to 20,
* allowAutoTopicCreation defaults to false, clientId defaults to
* "Uptime-Kuma" and ssl defaults to false)
* @param {SASLOptions} saslOptions Options for kafka client
* Authentication (SASL) (defaults to {})
* @returns {Promise<string>} Status message
*/
exports.kafkaProducerAsync = function (brokers, topic, message, options = {}, saslOptions = {}) {
return new Promise((resolve, reject) => {
@ -332,9 +333,9 @@ exports.kafkaProducerAsync = function (brokers, topic, message, options = {}, sa
/**
* Use NTLM Auth for a http request.
* @param {Object} options The http request options
* @param {Object} ntlmOptions The auth options
* @returns {Promise<(string[]|Object[]|Object)>}
* @param {object} options The http request options
* @param {object} ntlmOptions The auth options
* @returns {Promise<(string[] | object[] | object)>} NTLM response
*/
exports.httpNtlm = function (options, ntlmOptions) {
return new Promise((resolve, reject) => {
@ -356,7 +357,7 @@ exports.httpNtlm = function (options, ntlmOptions) {
* @param {string} resolverServer The DNS server to use
* @param {string} resolverPort Port the DNS server is listening on
* @param {string} rrtype The type of record to request
* @returns {Promise<(string[]|Object[]|Object)>}
* @returns {Promise<(string[] | object[] | object)>} DNS response
*/
exports.dnsResolve = function (hostname, resolverServer, resolverPort, rrtype) {
const resolver = new Resolver();
@ -389,7 +390,8 @@ exports.dnsResolve = function (hostname, resolverServer, resolverPort, rrtype) {
* Run a query on SQL Server
* @param {string} connectionString The database connection string
* @param {string} query The query to validate the database with
* @returns {Promise<(string[]|Object[]|Object)>}
* @returns {Promise<(string[] | object[] | object)>} Response from
* server
*/
exports.mssqlQuery = async function (connectionString, query) {
let pool;
@ -413,7 +415,8 @@ exports.mssqlQuery = async function (connectionString, query) {
* Run a query on Postgres
* @param {string} connectionString The database connection string
* @param {string} query The query to validate the database with
* @returns {Promise<(string[]|Object[]|Object)>}
* @returns {Promise<(string[] | object[] | object)>} Response from
* server
*/
exports.postgresQuery = function (connectionString, query) {
return new Promise((resolve, reject) => {
@ -470,7 +473,7 @@ exports.postgresQuery = function (connectionString, query) {
* @param {string} connectionString The database connection string
* @param {string} query The query to validate the database with
* @param {?string} password The password to use
* @returns {Promise<(string)>}
* @returns {Promise<(string)>} Response from server
*/
exports.mysqlQuery = function (connectionString, query, password = undefined) {
return new Promise((resolve, reject) => {
@ -504,9 +507,10 @@ exports.mysqlQuery = function (connectionString, query, password = undefined) {
};
/**
* Connect to and Ping a MongoDB database
* Connect to and ping a MongoDB database
* @param {string} connectionString The database connection string
* @returns {Promise<(string[]|Object[]|Object)>}
* @returns {Promise<(string[] | object[] | object)>} Response from
* server
*/
exports.mongodbPing = async function (connectionString) {
let client = await MongoClient.connect(connectionString);
@ -528,9 +532,9 @@ exports.mongodbPing = async function (connectionString) {
* @param {string} calledStationId ID of called station
* @param {string} callingStationId ID of calling station
* @param {string} secret Secret to use
* @param {number} [port=1812] Port to contact radius server on
* @param {number} [timeout=2500] Timeout for connection to use
* @returns {Promise<any>}
* @param {number} port Port to contact radius server on
* @param {number} timeout Timeout for connection to use
* @returns {Promise<any>} Response from server
*/
exports.radius = function (
hostname,
@ -570,6 +574,7 @@ exports.radius = function (
/**
* Redis server ping
* @param {string} dsn The redis connection string
* @returns {Promise<any>} Response from redis server
*/
exports.redisPingAsync = function (dsn) {
return new Promise((resolve, reject) => {
@ -611,7 +616,7 @@ exports.setting = async function (key) {
};
/**
* Sets the specified setting to specifed value
* Sets the specified setting to specified value
* @param {string} key Key of setting to set
* @param {any} value Value to set to
* @param {?string} type Type of setting
@ -624,7 +629,7 @@ exports.setSetting = async function (key, value, type = null) {
/**
* Get settings based on type
* @param {string} type The type of setting
* @returns {Promise<Bean>}
* @returns {Promise<Bean>} Settings of requested type
*/
exports.getSettings = async function (type) {
return await Settings.getSettings(type);
@ -633,7 +638,7 @@ exports.getSettings = async function (type) {
/**
* Set settings based on type
* @param {string} type Type of settings to set
* @param {Object} data Values of settings
* @param {object} data Values of settings
* @returns {Promise<void>}
*/
exports.setSettings = async function (type, data) {
@ -647,7 +652,7 @@ exports.setSettings = async function (type, data) {
* Get number of days between two dates
* @param {Date} validFrom Start date
* @param {Date} validTo End date
* @returns {number}
* @returns {number} Number of days
*/
const getDaysBetween = (validFrom, validTo) =>
Math.round(Math.abs(+validFrom - +validTo) / 8.64e7);
@ -656,7 +661,7 @@ const getDaysBetween = (validFrom, validTo) =>
* Get days remaining from a time range
* @param {Date} validFrom Start date
* @param {Date} validTo End date
* @returns {number}
* @returns {number} Number of days remaining
*/
const getDaysRemaining = (validFrom, validTo) => {
const daysRemaining = getDaysBetween(validFrom, validTo);
@ -668,8 +673,9 @@ const getDaysRemaining = (validFrom, validTo) => {
/**
* Fix certificate info for display
* @param {Object} info The chain obtained from getPeerCertificate()
* @returns {Object} An object representing certificate information
* @param {object} info The chain obtained from getPeerCertificate()
* @returns {object} An object representing certificate information
* @throws The certificate chain length exceeded 500.
*/
const parseCertificateInfo = function (info) {
let link = info;
@ -716,8 +722,9 @@ const parseCertificateInfo = function (info) {
/**
* Check if certificate is valid
* @param {Object} res Response object from axios
* @returns {Object} Object containing certificate information
* @param {object} res Response object from axios
* @returns {object} Object containing certificate information
* @throws No socket was found to check certificate for
*/
exports.checkCertificate = function (res) {
if (!res.request.res.socket) {
@ -775,7 +782,7 @@ exports.checkStatusCode = function (status, acceptedCodes) {
* Get total number of clients in room
* @param {Server} io Socket server instance
* @param {string} roomName Name of room to check
* @returns {number}
* @returns {number} Total clients in room
*/
exports.getTotalClientInRoom = (io, roomName) => {
@ -802,7 +809,8 @@ exports.getTotalClientInRoom = (io, roomName) => {
/**
* Allow CORS all origins if development
* @param {Object} res Response object from axios
* @param {object} res Response object from axios
* @returns {void}
*/
exports.allowDevAllOrigin = (res) => {
if (process.env.NODE_ENV === "development") {
@ -812,16 +820,20 @@ exports.allowDevAllOrigin = (res) => {
/**
* Allow CORS all origins
* @param {Object} res Response object from axios
* @param {object} res Response object from axios
* @returns {void}
*/
exports.allowAllOrigin = (res) => {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, OPTIONS");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
};
/**
* Check if a user is logged in
* @param {Socket} socket Socket instance
* @returns {void}
* @throws The user is not logged in
*/
exports.checkLogin = (socket) => {
if (!socket.userID) {
@ -832,8 +844,10 @@ exports.checkLogin = (socket) => {
/**
* For logged-in users, double-check the password
* @param {Socket} socket Socket.io instance
* @param {string} currentPassword
* @returns {Promise<Bean>}
* @param {string} currentPassword Password to validate
* @returns {Promise<Bean>} User
* @throws The current password is not a string
* @throws The provided password is not correct
*/
exports.doubleCheckPassword = async (socket, currentPassword) => {
if (typeof currentPassword !== "string") {
@ -851,27 +865,10 @@ exports.doubleCheckPassword = async (socket, currentPassword) => {
return user;
};
/** Start Unit tests */
exports.startUnitTest = async () => {
console.log("Starting unit test...");
const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm";
const child = childProcess.spawn(npm, [ "run", "jest-backend" ]);
child.stdout.on("data", (data) => {
console.log(data.toString());
});
child.stderr.on("data", (data) => {
console.log(data.toString());
});
child.on("close", function (code) {
console.log("Jest exit code: " + code);
process.exit(code);
});
};
/** Start end-to-end tests */
/**
* Start end-to-end tests
* @returns {void}
*/
exports.startE2eTests = async () => {
console.log("Starting unit test...");
const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm";
@ -894,7 +891,7 @@ exports.startE2eTests = async () => {
/**
* Convert unknown string to UTF8
* @param {Uint8Array} body Buffer
* @returns {string}
* @returns {string} UTF8 string
*/
exports.convertToUTF8 = (body) => {
const guessEncoding = chardet.detect(body);
@ -906,11 +903,10 @@ exports.convertToUTF8 = (body) => {
* Returns a color code in hex format based on a given percentage:
* 0% => hue = 10 => red
* 100% => hue = 90 => green
*
* @param {number} percentage float, 0 to 1
* @param {number} maxHue
* @param {number} minHue, int
* @returns {string}, hex value
* @param {number} maxHue Maximum hue - int
* @param {number} minHue Minimum hue - int
* @returns {string} Color in hex
*/
exports.percentageToColor = (percentage, maxHue = 90, minHue = 10) => {
const hue = percentage * (maxHue - minHue) + minHue;
@ -923,10 +919,9 @@ exports.percentageToColor = (percentage, maxHue = 90, minHue = 10) => {
/**
* Joins and array of string to one string after filtering out empty values
*
* @param {string[]} parts
* @param {string} connector
* @returns {string}
* @param {string[]} parts Strings to join
* @param {string} connector Separator for joined strings
* @returns {string} Joined strings
*/
exports.filterAndJoin = (parts, connector = "") => {
return parts.filter((part) => !!part && part !== "").join(connector);
@ -934,8 +929,9 @@ exports.filterAndJoin = (parts, connector = "") => {
/**
* Send an Error response
* @param {Object} res Express response object
* @param {string} [msg=""] Message to send
* @param {object} res Express response object
* @param {string} msg Message to send
* @returns {void}
*/
module.exports.sendHttpError = (res, msg = "") => {
if (msg.includes("SQLITE_BUSY") || msg.includes("SQLITE_LOCKED")) {
@ -956,6 +952,13 @@ module.exports.sendHttpError = (res, msg = "") => {
}
};
/**
* Convert timezone of time object
* @param {object} obj Time object to update
* @param {string} timezone New timezone to set
* @param {boolean} timeObjectToUTC Convert time object to UTC
* @returns {object} Time object with updated timezone
*/
function timeObjectConvertTimezone(obj, timezone, timeObjectToUTC = true) {
let offsetString;
@ -998,20 +1001,20 @@ function timeObjectConvertTimezone(obj, timezone, timeObjectToUTC = true) {
}
/**
*
* @param {object} obj
* @param {string} timezone
* @returns {object}
* Convert time object to UTC
* @param {object} obj Object to convert
* @param {string} timezone Timezone of time object
* @returns {object} Updated time object
*/
module.exports.timeObjectToUTC = (obj, timezone = undefined) => {
return timeObjectConvertTimezone(obj, timezone, true);
};
/**
*
* @param {object} obj
* @param {string} timezone
* @returns {object}
* Convert time object to local time
* @param {object} obj Object to convert
* @param {string} timezone Timezone to convert to
* @returns {object} Updated object
*/
module.exports.timeObjectToLocal = (obj, timezone = undefined) => {
return timeObjectConvertTimezone(obj, timezone, false);
@ -1019,7 +1022,8 @@ module.exports.timeObjectToLocal = (obj, timezone = undefined) => {
/**
* Create gRPC client stib
* @param {Object} options from gRPC client
* @param {object} options from gRPC client
* @returns {Promise<object>} Result of gRPC query
*/
module.exports.grpcQuery = async (options) => {
const { grpcUrl, grpcProtobufData, grpcServiceName, grpcEnableTls, grpcMethod, grpcBody } = options;
@ -1101,10 +1105,9 @@ module.exports.rootCertificatesFingerprints = () => {
module.exports.SHAKE256_LENGTH = 16;
/**
*
* @param {string} data
* @param {number} len
* @return {string}
* @param {string} data The data to be hashed
* @param {number} len Output length of the hash
* @returns {string} The hashed data in hex format
*/
module.exports.shake256 = (data, len) => {
if (!data) {
@ -1115,6 +1118,16 @@ module.exports.shake256 = (data, len) => {
.digest("hex");
};
/**
* Non await sleep
* Source: https://stackoverflow.com/questions/59099454/is-there-a-way-to-call-sleep-without-await-keyword
* @param {number} n Milliseconds to wait
* @returns {void}
*/
module.exports.wait = (n) => {
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, n);
};
// For unit test, export functions
if (process.env.TEST_BACKEND) {
module.exports.__test = {