This commit is contained in:
Toby Liddicoat 2025-02-27 20:25:14 +00:00 committed by GitHub
commit a9b71ce8ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 882 additions and 373 deletions

26
package-lock.json generated
View file

@ -61,6 +61,7 @@
"node-radius-client": "~1.0.0", "node-radius-client": "~1.0.0",
"nodemailer": "~6.9.13", "nodemailer": "~6.9.13",
"nostr-tools": "^2.10.4", "nostr-tools": "^2.10.4",
"notifications-node-client": "^8.2.1",
"notp": "~2.0.3", "notp": "~2.0.3",
"openid-client": "^5.4.2", "openid-client": "^5.4.2",
"password-hash": "~1.2.2", "password-hash": "~1.2.2",
@ -12732,6 +12733,31 @@
"license": "MIT", "license": "MIT",
"optional": true "optional": true
}, },
"node_modules/notifications-node-client": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/notifications-node-client/-/notifications-node-client-8.2.1.tgz",
"integrity": "sha512-wyZh/NbjN8S2uQX18utYtCyC726BBaGeTc4HeUpdhZv5sYKuaQY94N31v9syh8SzVgehyMzW37y08EePmi+k3Q==",
"license": "MIT",
"dependencies": {
"axios": "^1.7.2",
"jsonwebtoken": "^9.0.2"
},
"engines": {
"node": ">=14.17.3",
"npm": ">=6.14.13"
}
},
"node_modules/notifications-node-client/node_modules/axios": {
"version": "1.7.9",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/notp": { "node_modules/notp": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/notp/-/notp-2.0.3.tgz", "resolved": "https://registry.npmjs.org/notp/-/notp-2.0.3.tgz",

View file

@ -119,6 +119,7 @@
"node-radius-client": "~1.0.0", "node-radius-client": "~1.0.0",
"nodemailer": "~6.9.13", "nodemailer": "~6.9.13",
"nostr-tools": "^2.10.4", "nostr-tools": "^2.10.4",
"notifications-node-client": "^8.2.1",
"notp": "~2.0.3", "notp": "~2.0.3",
"openid-client": "^5.4.2", "openid-client": "^5.4.2",
"password-hash": "~1.2.2", "password-hash": "~1.2.2",

View file

@ -3,7 +3,10 @@ const passwordHash = require("./password-hash");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const { setting } = require("./util-server"); const { setting } = require("./util-server");
const { log } = require("../src/util"); const { log } = require("../src/util");
const { loginRateLimiter, apiRateLimiter } = require("./rate-limiter"); const {
loginRateLimiter,
apiRateLimiter,
} = require("./rate-limiter");
const { Settings } = require("./settings"); const { Settings } = require("./settings");
const dayjs = require("dayjs"); const dayjs = require("dayjs");

View file

@ -47,7 +47,8 @@ async function sendNotificationList(socket) {
*/ */
async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite = false) { async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite = false) {
let list = await R.getAll(` let list = await R.getAll(`
SELECT * FROM heartbeat SELECT *
FROM heartbeat
WHERE monitor_id = ? WHERE monitor_id = ?
ORDER BY time DESC ORDER BY time DESC
LIMIT 100 LIMIT 100

View file

@ -1,7 +1,13 @@
const fs = require("fs"); const fs = require("fs");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const { setSetting, setting } = require("./util-server"); const {
const { log, sleep } = require("../src/util"); setSetting,
setting,
} = require("./util-server");
const {
log,
sleep,
} = require("../src/util");
const knex = require("knex"); const knex = require("knex");
const path = require("path"); const path = require("path");
const { EmbeddedMariaDB } = require("./embedded-mariadb"); const { EmbeddedMariaDB } = require("./embedded-mariadb");
@ -136,24 +142,24 @@ class Database {
Database.dataDir = process.env.DATA_DIR || args["data-dir"] || "./data/"; Database.dataDir = process.env.DATA_DIR || args["data-dir"] || "./data/";
Database.sqlitePath = path.join(Database.dataDir, "kuma.db"); Database.sqlitePath = path.join(Database.dataDir, "kuma.db");
if (! fs.existsSync(Database.dataDir)) { if (!fs.existsSync(Database.dataDir)) {
fs.mkdirSync(Database.dataDir, { recursive: true }); fs.mkdirSync(Database.dataDir, { recursive: true });
} }
Database.uploadDir = path.join(Database.dataDir, "upload/"); Database.uploadDir = path.join(Database.dataDir, "upload/");
if (! fs.existsSync(Database.uploadDir)) { if (!fs.existsSync(Database.uploadDir)) {
fs.mkdirSync(Database.uploadDir, { recursive: true }); fs.mkdirSync(Database.uploadDir, { recursive: true });
} }
// Create screenshot dir // Create screenshot dir
Database.screenshotDir = path.join(Database.dataDir, "screenshots/"); Database.screenshotDir = path.join(Database.dataDir, "screenshots/");
if (! fs.existsSync(Database.screenshotDir)) { if (!fs.existsSync(Database.screenshotDir)) {
fs.mkdirSync(Database.screenshotDir, { recursive: true }); fs.mkdirSync(Database.screenshotDir, { recursive: true });
} }
Database.dockerTLSDir = path.join(Database.dataDir, "docker-tls/"); Database.dockerTLSDir = path.join(Database.dataDir, "docker-tls/");
if (! fs.existsSync(Database.dockerTLSDir)) { if (!fs.existsSync(Database.dockerTLSDir)) {
fs.mkdirSync(Database.dockerTLSDir, { recursive: true }); fs.mkdirSync(Database.dockerTLSDir, { recursive: true });
} }
@ -231,7 +237,7 @@ class Database {
if (dbConfig.type === "sqlite") { if (dbConfig.type === "sqlite") {
if (! fs.existsSync(Database.sqlitePath)) { if (!fs.existsSync(Database.sqlitePath)) {
log.info("server", "Copying Database"); log.info("server", "Copying Database");
fs.copyFileSync(Database.templatePath, Database.sqlitePath); fs.copyFileSync(Database.templatePath, Database.sqlitePath);
} }
@ -252,7 +258,7 @@ class Database {
idleTimeoutMillis: 120 * 1000, idleTimeoutMillis: 120 * 1000,
propagateCreateError: false, propagateCreateError: false,
acquireTimeoutMillis: acquireConnectionTimeout, acquireTimeoutMillis: acquireConnectionTimeout,
} },
}; };
} else if (dbConfig.type === "mariadb") { } else if (dbConfig.type === "mariadb") {
if (!/^\w+$/.test(dbConfig.dbName)) { if (!/^\w+$/.test(dbConfig.dbName)) {
@ -451,7 +457,7 @@ class Database {
static async patchSqlite() { static async patchSqlite() {
let version = parseInt(await setting("database_version")); let version = parseInt(await setting("database_version"));
if (! version) { if (!version) {
version = 0; version = 0;
} }
@ -502,7 +508,7 @@ class Database {
log.debug("db", "Database Patch 2.0 Process"); log.debug("db", "Database Patch 2.0 Process");
let databasePatchedFiles = await setting("databasePatchedFiles"); let databasePatchedFiles = await setting("databasePatchedFiles");
if (! databasePatchedFiles) { if (!databasePatchedFiles) {
databasePatchedFiles = {}; databasePatchedFiles = {};
} }
@ -579,11 +585,11 @@ class Database {
let id = await R.store(statusPage); let id = await R.store(statusPage);
await R.exec("UPDATE incident SET status_page_id = ? WHERE status_page_id IS NULL", [ await R.exec("UPDATE incident SET status_page_id = ? WHERE status_page_id IS NULL", [
id id,
]); ]);
await R.exec("UPDATE [group] SET status_page_id = ? WHERE status_page_id IS NULL", [ await R.exec("UPDATE [group] SET status_page_id = ? WHERE status_page_id IS NULL", [
id id,
]); ]);
await R.exec("DELETE FROM setting WHERE type = 'statusPage'"); await R.exec("DELETE FROM setting WHERE type = 'statusPage'");
@ -611,13 +617,13 @@ class Database {
static async patch2Recursion(sqlFilename, databasePatchedFiles) { static async patch2Recursion(sqlFilename, databasePatchedFiles) {
let value = this.patchList[sqlFilename]; let value = this.patchList[sqlFilename];
if (! value) { if (!value) {
log.info("db", sqlFilename + " skip"); log.info("db", sqlFilename + " skip");
return; return;
} }
// Check if patched // Check if patched
if (! databasePatchedFiles[sqlFilename]) { if (!databasePatchedFiles[sqlFilename]) {
log.info("db", sqlFilename + " is not patched"); log.info("db", sqlFilename + " is not patched");
if (value.parents) { if (value.parents) {
@ -652,7 +658,7 @@ class Database {
// Remove all comments (--) // Remove all comments (--)
let lines = text.split("\n"); let lines = text.split("\n");
lines = lines.filter((line) => { lines = lines.filter((line) => {
return ! line.startsWith("--"); return !line.startsWith("--");
}); });
// Split statements by semicolon // Split statements by semicolon
@ -797,7 +803,8 @@ class Database {
// Stop if stat_* tables are not empty // Stop if stat_* tables are not empty
for (let table of [ "stat_minutely", "stat_hourly", "stat_daily" ]) { for (let table of [ "stat_minutely", "stat_hourly", "stat_daily" ]) {
let countResult = await R.getRow(`SELECT COUNT(*) AS count FROM ${table}`); let countResult = await R.getRow(`SELECT COUNT(*) AS count
FROM ${table}`);
let count = countResult.count; let count = countResult.count;
if (count > 0) { if (count > 0) {
log.warn("db", `Aggregate table ${table} is not empty, migration will not be started (Maybe you were using 2.0.0-dev?)`); log.warn("db", `Aggregate table ${table} is not empty, migration will not be started (Maybe you were using 2.0.0-dev?)`);
@ -814,12 +821,12 @@ class Database {
for (let monitor of monitors) { for (let monitor of monitors) {
// Get a list of unique dates from the heartbeat table, using raw sql // Get a list of unique dates from the heartbeat table, using raw sql
let dates = await R.getAll(` let dates = await R.getAll(`
SELECT DISTINCT DATE(time) AS date SELECT DISTINCT DATE (time) AS date
FROM heartbeat FROM heartbeat
WHERE monitor_id = ? WHERE monitor_id = ?
ORDER BY date ASC ORDER BY date ASC
`, [ `, [
monitor.monitor_id monitor.monitor_id,
]); ]);
for (let date of dates) { for (let date of dates) {
@ -833,7 +840,7 @@ class Database {
SELECT status, ping, time SELECT status, ping, time
FROM heartbeat FROM heartbeat
WHERE monitor_id = ? WHERE monitor_id = ?
AND DATE(time) = ? AND DATE (time) = ?
ORDER BY time ASC ORDER BY time ASC
`, [ monitor.monitor_id, date.date ]); `, [ monitor.monitor_id, date.date ]);
@ -887,10 +894,12 @@ class Database {
log.info("db", "Deleting non-important heartbeats for monitor " + monitor.id); log.info("db", "Deleting non-important heartbeats for monitor " + monitor.id);
} }
await R.exec(` await R.exec(`
DELETE FROM heartbeat DELETE
FROM heartbeat
WHERE monitor_id = ? WHERE monitor_id = ?
AND important = 0 AND important = 0
AND time < ${sqlHourOffset} AND time
< ${sqlHourOffset}
AND id NOT IN ( AND id NOT IN (
SELECT id FROM ( -- written this way for Maria's support SELECT id FROM ( -- written this way for Maria's support
SELECT id SELECT id

View file

@ -146,7 +146,7 @@ class DockerHost {
static getHttpsAgentOptions(dockerType, url) { static getHttpsAgentOptions(dockerType, url) {
let baseOptions = { let baseOptions = {
maxCachedSessions: 0, maxCachedSessions: 0,
rejectUnauthorized: true rejectUnauthorized: true,
}; };
let certOptions = {}; let certOptions = {};
@ -163,13 +163,13 @@ class DockerHost {
certOptions = { certOptions = {
ca, ca,
key, key,
cert cert,
}; };
} }
return { return {
...baseOptions, ...baseOptions,
...certOptions ...certOptions,
}; };
} }
} }

View file

@ -15,7 +15,7 @@ const jobs = [
interval: "*/5 * * * *", interval: "*/5 * * * *",
jobFunc: incrementalVacuum, jobFunc: incrementalVacuum,
croner: null, croner: null,
} },
]; ];
/** /**
@ -54,5 +54,5 @@ const stopBackgroundJobs = function () {
module.exports = { module.exports = {
initBackgroundJobs, initBackgroundJobs,
stopBackgroundJobs stopBackgroundJobs,
}; };

View file

@ -1,5 +1,9 @@
const { BeanModel } = require("redbean-node/dist/bean-model"); const { BeanModel } = require("redbean-node/dist/bean-model");
const { parseTimeObject, parseTimeFromTimeObject, log } = require("../../src/util"); const {
parseTimeObject,
parseTimeFromTimeObject,
log,
} = require("../../src/util");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const dayjs = require("dayjs"); const dayjs = require("dayjs");
const Cron = require("croner"); const Cron = require("croner");
@ -192,7 +196,8 @@ class Maintenance extends BeanModel {
* @returns {void} * @returns {void}
*/ */
static validateCron(cron) { static validateCron(cron) {
let job = new Cron(cron, () => {}); let job = new Cron(cron, () => {
});
job.stop(); job.stop();
} }

View file

@ -1,11 +1,37 @@
const dayjs = require("dayjs"); const dayjs = require("dayjs");
const axios = require("axios"); const axios = require("axios");
const { Prometheus } = require("../prometheus"); const { Prometheus } = require("../prometheus");
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND, const {
SQL_DATETIME_FORMAT, evaluateJsonQuery log,
UP,
DOWN,
PENDING,
MAINTENANCE,
flipStatus,
MAX_INTERVAL_SECOND,
MIN_INTERVAL_SECOND,
SQL_DATETIME_FORMAT,
evaluateJsonQuery,
} = require("../../src/util"); } = require("../../src/util");
const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius, grpcQuery, const {
redisPingAsync, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal tcping,
ping,
checkCertificate,
checkStatusCode,
getTotalClientInRoom,
setting,
mssqlQuery,
postgresQuery,
mysqlQuery,
setSetting,
httpNtlm,
radius,
grpcQuery,
redisPingAsync,
kafkaProducerAsync,
getOidcTokenClientCredentials,
rootCertificatesFingerprints,
axiosAbortSignal,
} = require("../util-server"); } = require("../util-server");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const { BeanModel } = require("redbean-node/dist/bean-model"); const { BeanModel } = require("redbean-node/dist/bean-model");
@ -61,7 +87,10 @@ class Monitor extends BeanModel {
} }
if (certExpiry && (this.type === "http" || this.type === "keyword" || this.type === "json-query") && this.getURLProtocol() === "https:") { if (certExpiry && (this.type === "http" || this.type === "keyword" || this.type === "json-query") && this.getURLProtocol() === "https:") {
const { certExpiryDaysRemaining, validCert } = await this.getCertExpiry(this.id); const {
certExpiryDaysRemaining,
validCert,
} = await this.getCertExpiry(this.id);
obj.certExpiryDaysRemaining = certExpiryDaysRemaining; obj.certExpiryDaysRemaining = certExpiryDaysRemaining;
obj.validCert = validCert; obj.validCert = validCert;
} }
@ -218,13 +247,13 @@ class Monitor extends BeanModel {
if (tlsInfo?.valid && tlsInfo?.certInfo?.daysRemaining) { if (tlsInfo?.valid && tlsInfo?.certInfo?.daysRemaining) {
return { return {
certExpiryDaysRemaining: tlsInfo.certInfo.daysRemaining, certExpiryDaysRemaining: tlsInfo.certInfo.daysRemaining,
validCert: true validCert: true,
}; };
} }
} }
return { return {
certExpiryDaysRemaining: "", certExpiryDaysRemaining: "",
validCert: false validCert: false,
}; };
} }
@ -334,7 +363,7 @@ class Monitor extends BeanModel {
let beatInterval = this.interval; let beatInterval = this.interval;
if (! beatInterval) { if (!beatInterval) {
beatInterval = 1; beatInterval = 1;
} }
@ -479,7 +508,7 @@ class Monitor extends BeanModel {
...(contentType ? { "Content-Type": contentType } : {}), ...(contentType ? { "Content-Type": contentType } : {}),
...(basicAuthHeader), ...(basicAuthHeader),
...(oauth2AuthHeader), ...(oauth2AuthHeader),
...(this.headers ? JSON.parse(this.headers) : {}) ...(this.headers ? JSON.parse(this.headers) : {}),
}, },
maxRedirects: this.maxredirects, maxRedirects: this.maxredirects,
validateStatus: (status) => { validateStatus: (status) => {
@ -504,7 +533,10 @@ class Monitor extends BeanModel {
const proxy = await R.load("proxy", this.proxy_id); const proxy = await R.load("proxy", this.proxy_id);
if (proxy && proxy.active) { if (proxy && proxy.active) {
const { httpAgent, httpsAgent } = Proxy.createAgents(proxy, { const {
httpAgent,
httpsAgent,
} = Proxy.createAgents(proxy, {
httpsAgentOptions: httpsAgentOptions, httpsAgentOptions: httpsAgentOptions,
}); });
@ -518,7 +550,7 @@ class Monitor extends BeanModel {
let jar = new CookieJar(); let jar = new CookieJar();
let httpsCookieAgentOptions = { let httpsCookieAgentOptions = {
...httpsAgentOptions, ...httpsAgentOptions,
cookies: { jar } cookies: { jar },
}; };
options.httpsAgent = new HttpsCookieAgent(httpsCookieAgentOptions); options.httpsAgent = new HttpsCookieAgent(httpsCookieAgentOptions);
} }
@ -600,7 +632,10 @@ class Monitor extends BeanModel {
} else if (this.type === "json-query") { } else if (this.type === "json-query") {
let data = res.data; let data = res.data;
const { status, response } = await evaluateJsonQuery(data, this.jsonPath, this.jsonPathOperator, this.expectedValue); const {
status,
response,
} = await evaluateJsonQuery(data, this.jsonPath, this.jsonPathOperator, this.expectedValue);
if (status) { if (status) {
bean.status = UP; bean.status = UP;
@ -681,7 +716,7 @@ class Monitor extends BeanModel {
params: { params: {
filter: filter, filter: filter,
key: steamAPIKey, key: steamAPIKey,
} },
}); });
if (res.data.response && res.data.response.servers && res.data.response.servers.length > 0) { if (res.data.response && res.data.response.servers && res.data.response.servers.length > 0) {
@ -690,7 +725,8 @@ class Monitor extends BeanModel {
try { try {
bean.ping = await ping(this.hostname, this.packetSize); bean.ping = await ping(this.hostname, this.packetSize);
} catch (_) { } } catch (_) {
}
} else { } else {
throw new Error("Server not found on Steam"); throw new Error("Server not found on Steam");
} }
@ -739,7 +775,7 @@ class Monitor extends BeanModel {
} else if (dockerHost._dockerType === "tcp") { } else if (dockerHost._dockerType === "tcp") {
options.baseURL = DockerHost.patchDockerURL(dockerHost._dockerDaemon); options.baseURL = DockerHost.patchDockerURL(dockerHost._dockerDaemon);
options.httpsAgent = new https.Agent( options.httpsAgent = new https.Agent(
DockerHost.getHttpsAgentOptions(dockerHost._dockerType, options.baseURL) DockerHost.getHttpsAgentOptions(dockerHost._dockerType, options.baseURL),
); );
} }
@ -984,12 +1020,12 @@ class Monitor extends BeanModel {
previousBeat = bean; previousBeat = bean;
if (! this.isStop) { if (!this.isStop) {
log.debug("monitor", `[${this.name}] SetTimeout for next check.`); log.debug("monitor", `[${this.name}] SetTimeout for next check.`);
let intervalRemainingMs = Math.max( let intervalRemainingMs = Math.max(
1, 1,
beatInterval * 1000 - dayjs().diff(dayjs.utc(bean.time)) beatInterval * 1000 - dayjs().diff(dayjs.utc(bean.time)),
); );
log.debug("monitor", `[${this.name}] Next heartbeat in: ${intervalRemainingMs}ms`); log.debug("monitor", `[${this.name}] Next heartbeat in: ${intervalRemainingMs}ms`);
@ -1013,7 +1049,7 @@ class Monitor extends BeanModel {
UptimeKumaServer.errorLog(e, false); UptimeKumaServer.errorLog(e, false);
log.error("monitor", "Please report to https://github.com/louislam/uptime-kuma/issues"); log.error("monitor", "Please report to https://github.com/louislam/uptime-kuma/issues");
if (! this.isStop) { if (!this.isStop) {
log.info("monitor", "Try to restart the monitor"); log.info("monitor", "Try to restart the monitor");
this.heartbeatInterval = setTimeout(safeBeat, this.interval * 1000); this.heartbeatInterval = setTimeout(safeBeat, this.interval * 1000);
} }
@ -1047,7 +1083,7 @@ class Monitor extends BeanModel {
username: this.basic_auth_user, username: this.basic_auth_user,
password: this.basic_auth_pass, password: this.basic_auth_pass,
domain: this.authDomain, domain: this.authDomain,
workstation: this.authWorkstation ? this.authWorkstation : undefined workstation: this.authWorkstation ? this.authWorkstation : undefined,
}); });
} else { } else {
res = await axios.request(options); res = await axios.request(options);
@ -1065,8 +1101,9 @@ class Monitor extends BeanModel {
let oauth2AuthHeader = { let oauth2AuthHeader = {
"Authorization": this.oauthAccessToken.token_type + " " + this.oauthAccessToken.access_token, "Authorization": this.oauthAccessToken.token_type + " " + this.oauthAccessToken.access_token,
}; };
options.headers = { ...(options.headers), options.headers = {
...(oauth2AuthHeader) ...(options.headers),
...(oauth2AuthHeader),
}; };
return this.makeAxiosRequest(options, true); return this.makeAxiosRequest(options, true);
@ -1158,7 +1195,7 @@ class Monitor extends BeanModel {
if (oldCertInfo.certInfo.fingerprint256 !== checkCertificateResult.certInfo.fingerprint256) { if (oldCertInfo.certInfo.fingerprint256 !== checkCertificateResult.certInfo.fingerprint256) {
log.debug("monitor", "Resetting sent_history"); log.debug("monitor", "Resetting sent_history");
await R.exec("DELETE FROM notification_sent_history WHERE type = 'certificate' AND monitor_id = ?", [ await R.exec("DELETE FROM notification_sent_history WHERE type = 'certificate' AND monitor_id = ?", [
this.id this.id,
]); ]);
} else { } else {
log.debug("monitor", "No need to reset sent_history"); log.debug("monitor", "No need to reset sent_history");
@ -1168,7 +1205,8 @@ class Monitor extends BeanModel {
} else { } else {
log.debug("monitor", "Not valid object"); log.debug("monitor", "Not valid object");
} }
} catch (e) { } } catch (e) {
}
} }
@ -1326,8 +1364,9 @@ class Monitor extends BeanModel {
for (let notification of notificationList) { for (let notification of notificationList) {
try { try {
const heartbeatJSON = bean.toJSON(); const heartbeatJSON = bean.toJSON();
const monitorData = [{ id: monitor.id, const monitorData = [{
active: monitor.active id: monitor.id,
active: monitor.active,
}]; }];
const preloadData = await Monitor.preparePreloadData(monitorData); const preloadData = await Monitor.preparePreloadData(monitorData);
// Prevent if the msg is undefined, notifications such as Discord cannot send out. // Prevent if the msg is undefined, notifications such as Discord cannot send out.
@ -1370,7 +1409,7 @@ class Monitor extends BeanModel {
if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) { if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) {
const notificationList = await Monitor.getNotificationList(this); const notificationList = await Monitor.getNotificationList(this);
if (! notificationList.length > 0) { if (!notificationList.length > 0) {
// fail fast. If no notification is set, all the following checks can be skipped. // fail fast. If no notification is set, all the following checks can be skipped.
log.debug("monitor", "No notification, no need to send cert notification"); log.debug("monitor", "No notification, no need to send cert notification");
return; return;
@ -1458,7 +1497,7 @@ class Monitor extends BeanModel {
*/ */
static async getPreviousHeartbeat(monitorID) { static async getPreviousHeartbeat(monitorID) {
return await R.findOne("heartbeat", " id = (select MAX(id) from heartbeat where monitor_id = ?)", [ return await R.findOne("heartbeat", " id = (select MAX(id) from heartbeat where monitor_id = ?)", [
monitorID monitorID,
]); ]);
} }
@ -1570,7 +1609,7 @@ class Monitor extends BeanModel {
monitor_id: row.monitor_id, monitor_id: row.monitor_id,
value: row.value, value: row.value,
name: row.name, name: row.name,
color: row.color color: row.color,
}); });
}); });
@ -1687,7 +1726,7 @@ class Monitor extends BeanModel {
*/ */
static async unlinkAllChildren(groupID) { static async unlinkAllChildren(groupID) {
return await R.exec("UPDATE `monitor` SET parent = ? WHERE parent = ? ", [ return await R.exec("UPDATE `monitor` SET parent = ? WHERE parent = ? ", [
null, groupID null, groupID,
]); ]);
} }

View file

@ -8,7 +8,15 @@ const { marked } = require("marked");
const { Feed } = require("feed"); const { Feed } = require("feed");
const config = require("../config"); const config = require("../config");
const { STATUS_PAGE_ALL_DOWN, STATUS_PAGE_ALL_UP, STATUS_PAGE_MAINTENANCE, STATUS_PAGE_PARTIAL_DOWN, UP, MAINTENANCE, DOWN } = require("../../src/util"); const {
STATUS_PAGE_ALL_DOWN,
STATUS_PAGE_ALL_UP,
STATUS_PAGE_MAINTENANCE,
STATUS_PAGE_PARTIAL_DOWN,
UP,
MAINTENANCE,
DOWN,
} = require("../../src/util");
class StatusPage extends BeanModel { class StatusPage extends BeanModel {
@ -16,7 +24,7 @@ class StatusPage extends BeanModel {
* Like this: { "test-uptime.kuma.pet": "default" } * Like this: { "test-uptime.kuma.pet": "default" }
* @type {{}} * @type {{}}
*/ */
static domainMappingList = { }; static domainMappingList = {};
/** /**
* Handle responses to RSS pages * Handle responses to RSS pages
@ -26,7 +34,7 @@ class StatusPage extends BeanModel {
*/ */
static async handleStatusPageRSSResponse(response, slug) { static async handleStatusPageRSSResponse(response, slug) {
let statusPage = await R.findOne("status_page", " slug = ? ", [ let statusPage = await R.findOne("status_page", " slug = ? ", [
slug slug,
]); ]);
if (statusPage) { if (statusPage) {
@ -51,7 +59,7 @@ class StatusPage extends BeanModel {
} }
let statusPage = await R.findOne("status_page", " slug = ? ", [ let statusPage = await R.findOne("status_page", " slug = ? ", [
slug slug,
]); ]);
if (statusPage) { if (statusPage) {
@ -68,7 +76,10 @@ class StatusPage extends BeanModel {
* @returns {Promise<string>} the rendered html * @returns {Promise<string>} the rendered html
*/ */
static async renderRSS(statusPage, slug) { static async renderRSS(statusPage, slug) {
const { heartbeats, statusDescription } = await StatusPage.getRSSPageData(statusPage); const {
heartbeats,
statusDescription,
} = await StatusPage.getRSSPageData(statusPage);
let proto = config.isSSL ? "https" : "http"; let proto = config.isSSL ? "https" : "http";
let host = `${proto}://${config.hostname || "localhost"}:${config.port}/status/${slug}`; let host = `${proto}://${config.hostname || "localhost"}:${config.port}/status/${slug}`;
@ -135,7 +146,7 @@ class StatusPage extends BeanModel {
// Preload data // Preload data
// Add jsesc, fix https://github.com/louislam/uptime-kuma/issues/2186 // Add jsesc, fix https://github.com/louislam/uptime-kuma/issues/2186
const escapedJSONObject = jsesc(await StatusPage.getStatusPageData(statusPage), { const escapedJSONObject = jsesc(await StatusPage.getStatusPageData(statusPage), {
"isScriptContext": true "isScriptContext": true,
}); });
const script = $(` const script = $(`
@ -174,7 +185,7 @@ class StatusPage extends BeanModel {
} }
} }
if (! hasUp) { if (!hasUp) {
status = STATUS_PAGE_ALL_DOWN; status = STATUS_PAGE_ALL_DOWN;
} }
@ -223,7 +234,7 @@ class StatusPage extends BeanModel {
const showTags = !!statusPage.show_tags; const showTags = !!statusPage.show_tags;
const list = await R.find("group", " public = 1 AND status_page_id = ? ORDER BY weight ", [ const list = await R.find("group", " public = 1 AND status_page_id = ? ORDER BY weight ", [
statusPage.id statusPage.id,
]); ]);
let heartbeats = []; let heartbeats = [];
@ -236,7 +247,7 @@ class StatusPage extends BeanModel {
heartbeats.push({ heartbeats.push({
...monitor, ...monitor,
status: heartbeat.status, status: heartbeat.status,
time: heartbeat.time time: heartbeat.time,
}); });
} }
} }
@ -251,7 +262,7 @@ class StatusPage extends BeanModel {
return { return {
heartbeats, heartbeats,
statusDescription statusDescription,
}; };
} }
@ -279,7 +290,7 @@ class StatusPage extends BeanModel {
const showTags = !!statusPage.show_tags; const showTags = !!statusPage.show_tags;
const list = await R.find("group", " public = 1 AND status_page_id = ? ORDER BY weight ", [ const list = await R.find("group", " public = 1 AND status_page_id = ? ORDER BY weight ", [
statusPage.id statusPage.id,
]); ]);
for (let groupBean of list) { for (let groupBean of list) {
@ -442,7 +453,7 @@ class StatusPage extends BeanModel {
*/ */
static async slugToID(slug) { static async slugToID(slug) {
return await R.getCell("SELECT id FROM status_page WHERE slug = ? ", [ return await R.getCell("SELECT id FROM status_page WHERE slug = ? ", [
slug slug,
]); ]);
} }

View file

@ -2,7 +2,10 @@ const { BeanModel } = require("redbean-node/dist/bean-model");
const passwordHash = require("../password-hash"); const passwordHash = require("../password-hash");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const jwt = require("jsonwebtoken"); const jwt = require("jsonwebtoken");
const { shake256, SHAKE256_LENGTH } = require("../util-server"); const {
shake256,
SHAKE256_LENGTH,
} = require("../util-server");
class User extends BeanModel { class User extends BeanModel {
/** /**
@ -15,7 +18,7 @@ class User extends BeanModel {
static async resetPassword(userID, newPassword) { static async resetPassword(userID, newPassword) {
await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [ await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [
passwordHash.generate(newPassword), passwordHash.generate(newPassword),
userID userID,
]); ]);
} }
@ -29,7 +32,7 @@ class User extends BeanModel {
await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [ await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [
hashedPassword, hashedPassword,
this.id this.id,
]); ]);
this.password = hashedPassword; this.password = hashedPassword;

View file

@ -100,7 +100,7 @@ function ApiCache() {
* Generated by Trelent * Generated by Trelent
*/ */
function debug(a, b, c, d) { function debug(a, b, c, d) {
let arr = ["\x1b[36m[apicache]\x1b[0m", a, b, c, d].filter(function (arg) { let arr = [ "\x1b[36m[apicache]\x1b[0m", a, b, c, d ].filter(function (arg) {
return arg !== undefined; return arg !== undefined;
}); });
let debugEnv = process.env.DEBUG && process.env.DEBUG.split(",").indexOf("apicache") !== -1; let debugEnv = process.env.DEBUG && process.env.DEBUG.split(",").indexOf("apicache") !== -1;
@ -210,7 +210,8 @@ function ApiCache() {
try { try {
redis.hset(key, "response", JSON.stringify(value)); redis.hset(key, "response", JSON.stringify(value));
redis.hset(key, "duration", duration); redis.hset(key, "duration", duration);
redis.expire(key, duration / 1000, expireCallback || function () {}); redis.expire(key, duration / 1000, expireCallback || function () {
});
} catch (err) { } catch (err) {
debug("[apicache] error in redis.hset()"); debug("[apicache] error in redis.hset()");
} }
@ -247,8 +248,8 @@ function ApiCache() {
} }
res._apicache.content = Buffer.concat( res._apicache.content = Buffer.concat(
[oldContent, content], [ oldContent, content ],
oldContent.length + content.length oldContent.length + content.length,
); );
} else { } else {
res._apicache.content = content; res._apicache.content = content;
@ -314,7 +315,7 @@ function ApiCache() {
res.statusCode, res.statusCode,
headers, headers,
res._apicache.content, res._apicache.content,
encoding encoding,
); );
cacheResponse(key, cacheObject, duration); cacheResponse(key, cacheObject, duration);
@ -573,7 +574,8 @@ function ApiCache() {
* A Function for non tracking performance * A Function for non tracking performance
*/ */
function NOOPCachePerformance() { function NOOPCachePerformance() {
this.report = this.hit = this.miss = function () {}; // noop; this.report = this.hit = this.miss = function () {
}; // noop;
} }
/** /**
@ -826,7 +828,7 @@ function ApiCache() {
JSON.parse(obj.response), JSON.parse(obj.response),
middlewareToggle, middlewareToggle,
next, next,
duration duration,
); );
} else { } else {
perf.miss(key); perf.miss(key);
@ -837,7 +839,7 @@ function ApiCache() {
key, key,
duration, duration,
strDuration, strDuration,
middlewareToggle middlewareToggle,
); );
} }
}); });

View file

@ -2,7 +2,7 @@ const apicache = require("./apicache");
apicache.options({ apicache.options({
headerBlacklist: [ headerBlacklist: [
"cache-control" "cache-control",
], ],
headers: { headers: {
// Disable client side cache, only server side cache. // Disable client side cache, only server side cache.

View file

@ -22,7 +22,7 @@ MemoryCache.prototype.add = function (key, value, time, timeoutCallback) {
timeout: setTimeout(function () { timeout: setTimeout(function () {
instance.delete(key); instance.delete(key);
return timeoutCallback && typeof timeoutCallback === "function" && timeoutCallback(value, key); return timeoutCallback && typeof timeoutCallback === "function" && timeoutCallback(value, key);
}, time) }, time),
}; };
this.cache[key] = entry; this.cache[key] = entry;

View file

@ -1,4 +1,4 @@
'use strict'; "use strict";
// Original file https://raw.githubusercontent.com/elasticio/node-ntlm-client/master/lib/flags.js // Original file https://raw.githubusercontent.com/elasticio/node-ntlm-client/master/lib/flags.js
module.exports.NTLMFLAG_NEGOTIATE_UNICODE = 1 << 0; module.exports.NTLMFLAG_NEGOTIATE_UNICODE = 1 << 0;
/* Indicates that Unicode strings are supported for use in security buffer /* Indicates that Unicode strings are supported for use in security buffer

View file

@ -1,6 +1,7 @@
'use strict'; "use strict";
// Original source at https://github.com/elasticio/node-ntlm-client/blob/master/lib/hash.js // Original source at https://github.com/elasticio/node-ntlm-client/blob/master/lib/hash.js
var crypto = require('crypto'); var crypto = require("crypto");
function createLMResponse(challenge, lmhash) { function createLMResponse(challenge, lmhash) {
var buf = new Buffer.alloc(24), pwBuffer = new Buffer.alloc(21).fill(0); var buf = new Buffer.alloc(24), pwBuffer = new Buffer.alloc(21).fill(0);
lmhash.copy(pwBuffer); lmhash.copy(pwBuffer);
@ -9,19 +10,21 @@ function createLMResponse(challenge, lmhash) {
calculateDES(pwBuffer.slice(14), challenge).copy(buf, 16); calculateDES(pwBuffer.slice(14), challenge).copy(buf, 16);
return buf; return buf;
} }
function createLMHash(password) { function createLMHash(password) {
var buf = new Buffer.alloc(16), pwBuffer = new Buffer.alloc(14), magicKey = new Buffer.from('KGS!@#$%', 'ascii'); var buf = new Buffer.alloc(16), pwBuffer = new Buffer.alloc(14), magicKey = new Buffer.from("KGS!@#$%", "ascii");
if (password.length > 14) { if (password.length > 14) {
buf.fill(0); buf.fill(0);
return buf; return buf;
} }
pwBuffer.fill(0); pwBuffer.fill(0);
pwBuffer.write(password.toUpperCase(), 0, 'ascii'); pwBuffer.write(password.toUpperCase(), 0, "ascii");
return Buffer.concat([ return Buffer.concat([
calculateDES(pwBuffer.slice(0, 7), magicKey), calculateDES(pwBuffer.slice(0, 7), magicKey),
calculateDES(pwBuffer.slice(7), magicKey) calculateDES(pwBuffer.slice(7), magicKey),
]); ]);
} }
function calculateDES(key, message) { function calculateDES(key, message) {
var desKey = new Buffer.alloc(8); var desKey = new Buffer.alloc(8);
desKey[0] = key[0] & 0xFE; desKey[0] = key[0] & 0xFE;
@ -39,9 +42,10 @@ function calculateDES(key, message) {
} }
desKey[i] |= (parity % 2) === 0 ? 1 : 0; desKey[i] |= (parity % 2) === 0 ? 1 : 0;
} }
var des = crypto.createCipheriv('DES-ECB', desKey, ''); var des = crypto.createCipheriv("DES-ECB", desKey, "");
return des.update(message); return des.update(message);
} }
function createNTLMResponse(challenge, ntlmhash) { function createNTLMResponse(challenge, ntlmhash) {
var buf = new Buffer.alloc(24), ntlmBuffer = new Buffer.alloc(21).fill(0); var buf = new Buffer.alloc(24), ntlmBuffer = new Buffer.alloc(21).fill(0);
ntlmhash.copy(ntlmBuffer); ntlmhash.copy(ntlmBuffer);
@ -50,30 +54,36 @@ function createNTLMResponse(challenge, ntlmhash) {
calculateDES(ntlmBuffer.slice(14), challenge).copy(buf, 16); calculateDES(ntlmBuffer.slice(14), challenge).copy(buf, 16);
return buf; return buf;
} }
function createNTLMHash(password) { function createNTLMHash(password) {
var md4sum = crypto.createHash('md4'); var md4sum = crypto.createHash("md4");
md4sum.update(new Buffer.from(password, 'ucs2')); md4sum.update(new Buffer.from(password, "ucs2"));
return md4sum.digest(); return md4sum.digest();
} }
function createNTLMv2Hash(ntlmhash, username, authTargetName) { function createNTLMv2Hash(ntlmhash, username, authTargetName) {
var hmac = crypto.createHmac('md5', ntlmhash); var hmac = crypto.createHmac("md5", ntlmhash);
hmac.update(new Buffer.from(username.toUpperCase() + authTargetName, 'ucs2')); hmac.update(new Buffer.from(username.toUpperCase() + authTargetName, "ucs2"));
return hmac.digest(); return hmac.digest();
} }
function createLMv2Response(type2message, username, ntlmhash, nonce, targetName) { function createLMv2Response(type2message, username, ntlmhash, nonce, targetName) {
var buf = new Buffer.alloc(24), ntlm2hash = createNTLMv2Hash(ntlmhash, username, targetName), hmac = crypto.createHmac('md5', ntlm2hash); var buf = new Buffer.alloc(24), ntlm2hash = createNTLMv2Hash(ntlmhash, username, targetName),
hmac = crypto.createHmac("md5", ntlm2hash);
//server challenge //server challenge
type2message.challenge.copy(buf, 8); type2message.challenge.copy(buf, 8);
//client nonce //client nonce
buf.write(nonce || createPseudoRandomValue(16), 16, 'hex'); buf.write(nonce || createPseudoRandomValue(16), 16, "hex");
//create hash //create hash
hmac.update(buf.slice(8)); hmac.update(buf.slice(8));
var hashedBuffer = hmac.digest(); var hashedBuffer = hmac.digest();
hashedBuffer.copy(buf); hashedBuffer.copy(buf);
return buf; return buf;
} }
function createNTLMv2Response(type2message, username, ntlmhash, nonce, targetName) { function createNTLMv2Response(type2message, username, ntlmhash, nonce, targetName) {
var buf = new Buffer.alloc(48 + type2message.targetInfo.buffer.length), ntlm2hash = createNTLMv2Hash(ntlmhash, username, targetName), hmac = crypto.createHmac('md5', ntlm2hash); var buf = new Buffer.alloc(48 + type2message.targetInfo.buffer.length),
ntlm2hash = createNTLMv2Hash(ntlmhash, username, targetName), hmac = crypto.createHmac("md5", ntlm2hash);
//the first 8 bytes are spare to store the hashed value before the blob //the first 8 bytes are spare to store the hashed value before the blob
//server challenge //server challenge
type2message.challenge.copy(buf, 8); type2message.challenge.copy(buf, 8);
@ -86,12 +96,12 @@ function createNTLMv2Response(type2message, username, ntlmhash, nonce, targetNam
// maybe think about a different solution here // maybe think about a different solution here
// 11644473600000 = diff between 1970 and 1601 // 11644473600000 = diff between 1970 and 1601
var timestamp = ((Date.now() + 11644473600000) * 10000).toString(16); var timestamp = ((Date.now() + 11644473600000) * 10000).toString(16);
var timestampLow = Number('0x' + timestamp.substring(Math.max(0, timestamp.length - 8))); var timestampLow = Number("0x" + timestamp.substring(Math.max(0, timestamp.length - 8)));
var timestampHigh = Number('0x' + timestamp.substring(0, Math.max(0, timestamp.length - 8))); var timestampHigh = Number("0x" + timestamp.substring(0, Math.max(0, timestamp.length - 8)));
buf.writeUInt32LE(timestampLow, 24, false); buf.writeUInt32LE(timestampLow, 24, false);
buf.writeUInt32LE(timestampHigh, 28, false); buf.writeUInt32LE(timestampHigh, 28, false);
//random client nonce //random client nonce
buf.write(nonce || createPseudoRandomValue(16), 32, 'hex'); buf.write(nonce || createPseudoRandomValue(16), 32, "hex");
//zero //zero
buf.writeUInt32LE(0, 40); buf.writeUInt32LE(0, 40);
//complete target information block from type 2 message //complete target information block from type 2 message
@ -103,13 +113,15 @@ function createNTLMv2Response(type2message, username, ntlmhash, nonce, targetNam
hashedBuffer.copy(buf); hashedBuffer.copy(buf);
return buf; return buf;
} }
function createPseudoRandomValue(length) { function createPseudoRandomValue(length) {
var str = ''; var str = "";
while (str.length < length) { while (str.length < length) {
str += Math.floor(Math.random() * 16).toString(16); str += Math.floor(Math.random() * 16).toString(16);
} }
return str; return str;
} }
module.exports = { module.exports = {
createLMHash: createLMHash, createLMHash: createLMHash,
createNTLMHash: createNTLMHash, createNTLMHash: createNTLMHash,
@ -117,6 +129,6 @@ module.exports = {
createNTLMResponse: createNTLMResponse, createNTLMResponse: createNTLMResponse,
createLMv2Response: createLMv2Response, createLMv2Response: createLMv2Response,
createNTLMv2Response: createNTLMv2Response, createNTLMv2Response: createNTLMv2Response,
createPseudoRandomValue: createPseudoRandomValue createPseudoRandomValue: createPseudoRandomValue,
}; };
//# sourceMappingURL=hash.js.map //# sourceMappingURL=hash.js.map

View file

@ -1,13 +1,14 @@
'use strict'; "use strict";
// Original file https://raw.githubusercontent.com/elasticio/node-ntlm-client/master/lib/ntlm.js // Original file https://raw.githubusercontent.com/elasticio/node-ntlm-client/master/lib/ntlm.js
var os = require('os'), flags = require('./flags'), hash = require('./hash'); var os = require("os"), flags = require("./flags"), hash = require("./hash");
var NTLMSIGNATURE = "NTLMSSP\0"; var NTLMSIGNATURE = "NTLMSSP\0";
function createType1Message(workstation, target) { function createType1Message(workstation, target) {
var dataPos = 32, pos = 0, buf = new Buffer.alloc(1024); var dataPos = 32, pos = 0, buf = new Buffer.alloc(1024);
workstation = workstation === undefined ? os.hostname() : workstation; workstation = workstation === undefined ? os.hostname() : workstation;
target = target === undefined ? '' : target; target = target === undefined ? "" : target;
//signature //signature
buf.write(NTLMSIGNATURE, pos, NTLMSIGNATURE.length, 'ascii'); buf.write(NTLMSIGNATURE, pos, NTLMSIGNATURE.length, "ascii");
pos += NTLMSIGNATURE.length; pos += NTLMSIGNATURE.length;
//message type //message type
buf.writeUInt32LE(1, pos); buf.writeUInt32LE(1, pos);
@ -27,7 +28,7 @@ function createType1Message(workstation, target) {
buf.writeUInt32LE(target.length === 0 ? 0 : dataPos, pos); buf.writeUInt32LE(target.length === 0 ? 0 : dataPos, pos);
pos += 4; pos += 4;
if (target.length > 0) { if (target.length > 0) {
dataPos += buf.write(target, dataPos, 'ascii'); dataPos += buf.write(target, dataPos, "ascii");
} }
//workstation security buffer //workstation security buffer
buf.writeUInt16LE(workstation.length, pos); buf.writeUInt16LE(workstation.length, pos);
@ -37,39 +38,39 @@ function createType1Message(workstation, target) {
buf.writeUInt32LE(workstation.length === 0 ? 0 : dataPos, pos); buf.writeUInt32LE(workstation.length === 0 ? 0 : dataPos, pos);
pos += 4; pos += 4;
if (workstation.length > 0) { if (workstation.length > 0) {
dataPos += buf.write(workstation, dataPos, 'ascii'); dataPos += buf.write(workstation, dataPos, "ascii");
} }
return 'NTLM ' + buf.toString('base64', 0, dataPos); return "NTLM " + buf.toString("base64", 0, dataPos);
} }
function decodeType2Message(str) { function decodeType2Message(str) {
if (str === undefined) { if (str === undefined) {
throw new Error('Invalid argument'); throw new Error("Invalid argument");
} }
//convenience //convenience
if (Object.prototype.toString.call(str) !== '[object String]') { if (Object.prototype.toString.call(str) !== "[object String]") {
if (str.hasOwnProperty('headers') && str.headers.hasOwnProperty('www-authenticate')) { if (str.hasOwnProperty("headers") && str.headers.hasOwnProperty("www-authenticate")) {
str = str.headers['www-authenticate']; str = str.headers["www-authenticate"];
} } else {
else { throw new Error("Invalid argument");
throw new Error('Invalid argument');
} }
} }
var ntlmMatch = /^NTLM ([^,\s]+)/.exec(str); var ntlmMatch = /^NTLM ([^,\s]+)/.exec(str);
if (ntlmMatch) { if (ntlmMatch) {
str = ntlmMatch[1]; str = ntlmMatch[1];
} }
var buf = new Buffer.from(str, 'base64'), obj = {}; var buf = new Buffer.from(str, "base64"), obj = {};
//check signature //check signature
if (buf.toString('ascii', 0, NTLMSIGNATURE.length) !== NTLMSIGNATURE) { if (buf.toString("ascii", 0, NTLMSIGNATURE.length) !== NTLMSIGNATURE) {
throw new Error('Invalid message signature: ' + str); throw new Error("Invalid message signature: " + str);
} }
//check message type //check message type
if (buf.readUInt32LE(NTLMSIGNATURE.length) !== 2) { if (buf.readUInt32LE(NTLMSIGNATURE.length) !== 2) {
throw new Error('Invalid message type (no type 2)'); throw new Error("Invalid message type (no type 2)");
} }
//read flags //read flags
obj.flags = buf.readUInt32LE(20); obj.flags = buf.readUInt32LE(20);
obj.encoding = (obj.flags & flags.NTLMFLAG_NEGOTIATE_OEM) ? 'ascii' : 'ucs2'; obj.encoding = (obj.flags & flags.NTLMFLAG_NEGOTIATE_OEM) ? "ascii" : "ucs2";
obj.version = (obj.flags & flags.NTLMFLAG_NEGOTIATE_NTLM2_KEY) ? 2 : 1; obj.version = (obj.flags & flags.NTLMFLAG_NEGOTIATE_NTLM2_KEY) ? 2 : 1;
obj.challenge = buf.slice(24, 32); obj.challenge = buf.slice(24, 32);
//read target name //read target name
@ -78,10 +79,10 @@ function decodeType2Message(str) {
//skipping allocated space //skipping allocated space
var offset = buf.readUInt32LE(16); var offset = buf.readUInt32LE(16);
if (length === 0) { if (length === 0) {
return ''; return "";
} }
if ((offset + length) > buf.length || offset < 32) { if ((offset + length) > buf.length || offset < 32) {
throw new Error('Bad type 2 message'); throw new Error("Bad type 2 message");
} }
return buf.toString(obj.encoding, offset, offset + length); return buf.toString(obj.encoding, offset, offset + length);
})(); })();
@ -98,7 +99,7 @@ function decodeType2Message(str) {
return info; return info;
} }
if ((offset + length) > buf.length || offset < 32) { if ((offset + length) > buf.length || offset < 32) {
throw new Error('Bad type 2 message'); throw new Error("Bad type 2 message");
} }
var pos = offset; var pos = offset;
while (pos < (offset + length)) { while (pos < (offset + length)) {
@ -113,37 +114,38 @@ function decodeType2Message(str) {
var blockTypeStr = void 0; var blockTypeStr = void 0;
switch (blockType) { switch (blockType) {
case 1: case 1:
blockTypeStr = 'SERVER'; blockTypeStr = "SERVER";
break; break;
case 2: case 2:
blockTypeStr = 'DOMAIN'; blockTypeStr = "DOMAIN";
break; break;
case 3: case 3:
blockTypeStr = 'FQDN'; blockTypeStr = "FQDN";
break; break;
case 4: case 4:
blockTypeStr = 'DNS'; blockTypeStr = "DNS";
break; break;
case 5: case 5:
blockTypeStr = 'PARENT_DNS'; blockTypeStr = "PARENT_DNS";
break; break;
default: default:
blockTypeStr = ''; blockTypeStr = "";
break; break;
} }
if (blockTypeStr) { if (blockTypeStr) {
info[blockTypeStr] = buf.toString('ucs2', pos, pos + blockLength); info[blockTypeStr] = buf.toString("ucs2", pos, pos + blockLength);
} }
pos += blockLength; pos += blockLength;
} }
return { return {
parsed: info, parsed: info,
buffer: targetInfoBuffer buffer: targetInfoBuffer,
}; };
})(); })();
} }
return obj; return obj;
} }
function createType3Message(type2Message, username, password, workstation, target) { function createType3Message(type2Message, username, password, workstation, target) {
var dataPos = 52, buf = new Buffer.alloc(1024); var dataPos = 52, buf = new Buffer.alloc(1024);
if (workstation === undefined) { if (workstation === undefined) {
@ -153,12 +155,14 @@ function createType3Message(type2Message, username, password, workstation, targe
target = type2Message.targetName; target = type2Message.targetName;
} }
//signature //signature
buf.write(NTLMSIGNATURE, 0, NTLMSIGNATURE.length, 'ascii'); buf.write(NTLMSIGNATURE, 0, NTLMSIGNATURE.length, "ascii");
//message type //message type
buf.writeUInt32LE(3, 8); buf.writeUInt32LE(3, 8);
if (type2Message.version === 2) { if (type2Message.version === 2) {
dataPos = 64; dataPos = 64;
var ntlmHash = hash.createNTLMHash(password), nonce = hash.createPseudoRandomValue(16), lmv2 = hash.createLMv2Response(type2Message, username, ntlmHash, nonce, target), ntlmv2 = hash.createNTLMv2Response(type2Message, username, ntlmHash, nonce, target); var ntlmHash = hash.createNTLMHash(password), nonce = hash.createPseudoRandomValue(16),
lmv2 = hash.createLMv2Response(type2Message, username, ntlmHash, nonce, target),
ntlmv2 = hash.createNTLMv2Response(type2Message, username, ntlmHash, nonce, target);
//lmv2 security buffer //lmv2 security buffer
buf.writeUInt16LE(lmv2.length, 12); buf.writeUInt16LE(lmv2.length, 12);
buf.writeUInt16LE(lmv2.length, 14); buf.writeUInt16LE(lmv2.length, 14);
@ -171,9 +175,10 @@ function createType3Message(type2Message, username, password, workstation, targe
buf.writeUInt32LE(dataPos, 24); buf.writeUInt32LE(dataPos, 24);
ntlmv2.copy(buf, dataPos); ntlmv2.copy(buf, dataPos);
dataPos += ntlmv2.length; dataPos += ntlmv2.length;
} } else {
else { var lmHash = hash.createLMHash(password), ntlmHash = hash.createNTLMHash(password),
var lmHash = hash.createLMHash(password), ntlmHash = hash.createNTLMHash(password), lm = hash.createLMResponse(type2Message.challenge, lmHash), ntlm = hash.createNTLMResponse(type2Message.challenge, ntlmHash); lm = hash.createLMResponse(type2Message.challenge, lmHash),
ntlm = hash.createNTLMResponse(type2Message.challenge, ntlmHash);
//lm security buffer //lm security buffer
buf.writeUInt16LE(lm.length, 12); buf.writeUInt16LE(lm.length, 12);
buf.writeUInt16LE(lm.length, 14); buf.writeUInt16LE(lm.length, 14);
@ -188,18 +193,18 @@ function createType3Message(type2Message, username, password, workstation, targe
dataPos += ntlm.length; dataPos += ntlm.length;
} }
//target name security buffer //target name security buffer
buf.writeUInt16LE(type2Message.encoding === 'ascii' ? target.length : target.length * 2, 28); buf.writeUInt16LE(type2Message.encoding === "ascii" ? target.length : target.length * 2, 28);
buf.writeUInt16LE(type2Message.encoding === 'ascii' ? target.length : target.length * 2, 30); buf.writeUInt16LE(type2Message.encoding === "ascii" ? target.length : target.length * 2, 30);
buf.writeUInt32LE(dataPos, 32); buf.writeUInt32LE(dataPos, 32);
dataPos += buf.write(target, dataPos, type2Message.encoding); dataPos += buf.write(target, dataPos, type2Message.encoding);
//user name security buffer //user name security buffer
buf.writeUInt16LE(type2Message.encoding === 'ascii' ? username.length : username.length * 2, 36); buf.writeUInt16LE(type2Message.encoding === "ascii" ? username.length : username.length * 2, 36);
buf.writeUInt16LE(type2Message.encoding === 'ascii' ? username.length : username.length * 2, 38); buf.writeUInt16LE(type2Message.encoding === "ascii" ? username.length : username.length * 2, 38);
buf.writeUInt32LE(dataPos, 40); buf.writeUInt32LE(dataPos, 40);
dataPos += buf.write(username, dataPos, type2Message.encoding); dataPos += buf.write(username, dataPos, type2Message.encoding);
//workstation name security buffer //workstation name security buffer
buf.writeUInt16LE(type2Message.encoding === 'ascii' ? workstation.length : workstation.length * 2, 44); buf.writeUInt16LE(type2Message.encoding === "ascii" ? workstation.length : workstation.length * 2, 44);
buf.writeUInt16LE(type2Message.encoding === 'ascii' ? workstation.length : workstation.length * 2, 46); buf.writeUInt16LE(type2Message.encoding === "ascii" ? workstation.length : workstation.length * 2, 46);
buf.writeUInt32LE(dataPos, 48); buf.writeUInt32LE(dataPos, 48);
dataPos += buf.write(workstation, dataPos, type2Message.encoding); dataPos += buf.write(workstation, dataPos, type2Message.encoding);
if (type2Message.version === 2) { if (type2Message.version === 2) {
@ -210,11 +215,12 @@ function createType3Message(type2Message, username, password, workstation, targe
//flags //flags
buf.writeUInt32LE(type2Message.flags, 60); buf.writeUInt32LE(type2Message.flags, 60);
} }
return 'NTLM ' + buf.toString('base64', 0, dataPos); return "NTLM " + buf.toString("base64", 0, dataPos);
} }
module.exports = { module.exports = {
createType1Message: createType1Message, createType1Message: createType1Message,
decodeType2Message: decodeType2Message, decodeType2Message: decodeType2Message,
createType3Message: createType3Message createType3Message: createType3Message,
}; };
//# sourceMappingURL=ntlm.js.map //# sourceMappingURL=ntlm.js.map

View file

@ -1,57 +1,172 @@
"use strict"; "use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { var __createBinding = (this && this.__createBinding) || (Object.create ? (function (o, m, k, k2) {
if (k2 === undefined) k2 = k; if (k2 === undefined) {
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); k2 = k;
}) : (function(o, m, k, k2) { }
if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, {
enumerable: true,
get: function () {
return m[k];
},
});
}) : (function (o, m, k, k2) {
if (k2 === undefined) {
k2 = k;
}
o[k2] = m[k]; o[k2] = m[k];
})); }));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function (o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v }); Object.defineProperty(o, "default", {
}) : function(o, v) { enumerable: true,
value: v,
});
}) : function (o, v) {
o["default"] = v; o["default"] = v;
}); });
var __importStar = (this && this.__importStar) || function (mod) { var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod; if (mod && mod.__esModule) {
return mod;
}
var result = {}; var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); if (mod != null) {
for (var k in mod) {
if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) {
__createBinding(result, mod, k);
}
}
}
__setModuleDefault(result, mod); __setModuleDefault(result, mod);
return result; return result;
}; };
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } function adopt(value) {
return value instanceof P ? value : new P(function (resolve) {
resolve(value);
});
}
return new (P || (P = Promise))(function (resolve, reject) { return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function fulfilled(value) {
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } try {
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step(generator.next(value));
} catch (e) {
reject(e);
}
}
function rejected(value) {
try {
step(generator["throw"](value));
} catch (e) {
reject(e);
}
}
function step(result) {
result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
}
step((generator = generator.apply(thisArg, _arguments || [])).next()); step((generator = generator.apply(thisArg, _arguments || [])).next());
}); });
}; };
var __generator = (this && this.__generator) || function (thisArg, body) { var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; var _ = {
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; label: 0,
function verb(n) { return function (v) { return step([n, v]); }; } sent: function () {
if (t[0] & 1) {
throw t[1];
}
return t[1];
},
trys: [],
ops: [],
}, f, y, t, g;
return g = {
next: verb(0),
"throw": verb(1),
"return": verb(2),
}, typeof Symbol === "function" && (g[Symbol.iterator] = function () {
return this;
}), g;
function verb(n) {
return function (v) {
return step([ n, v ]);
};
}
function step(op) { function step(op) {
if (f) throw new TypeError("Generator is already executing."); if (f) {
while (_) try { throw new TypeError("Generator is already executing.");
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; }
if (y = 0, t) op = [op[0] & 2, t.value]; while (_) {
try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) {
return t;
}
if (y = 0, t) {
op = [ op[0] & 2, t.value ];
}
switch (op[0]) { switch (op[0]) {
case 0: case 1: t = op; break; case 0:
case 4: _.label++; return { value: op[1], done: false }; case 1:
case 5: _.label++; y = op[1]; op = [0]; continue; t = op;
case 7: op = _.ops.pop(); _.trys.pop(); continue; break;
case 4:
_.label++;
return {
value: op[1],
done: false,
};
case 5:
_.label++;
y = op[1];
op = [ 0 ];
continue;
case 7:
op = _.ops.pop();
_.trys.pop();
continue;
default: default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } _ = 0;
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } continue;
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } }
if (t[2]) _.ops.pop(); if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) {
_.trys.pop(); continue; _.label = op[1];
break;
}
if (op[0] === 6 && _.label < t[1]) {
_.label = t[1];
t = op;
break;
}
if (t && _.label < t[2]) {
_.label = t[2];
_.ops.push(op);
break;
}
if (t[2]) {
_.ops.pop();
}
_.trys.pop();
continue;
} }
op = body.call(thisArg, _); op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } } catch (e) {
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; op = [ 6, e ];
y = 0;
} finally {
f = t = 0;
}
}
if (op[0] & 5) {
throw op[1];
}
return {
value: op[0] ? op[1] : void 0,
done: true,
};
} }
}; };
var __importDefault = (this && this.__importDefault) || function (mod) { var __importDefault = (this && this.__importDefault) || function (mod) {
@ -64,12 +179,13 @@ var ntlm = __importStar(require("./ntlm"));
var https = __importStar(require("https")); var https = __importStar(require("https"));
var http = __importStar(require("http")); var http = __importStar(require("http"));
var dev_null_1 = __importDefault(require("dev-null")); var dev_null_1 = __importDefault(require("dev-null"));
/** /**
* @param credentials An NtlmCredentials object containing the username and password * @param credentials An NtlmCredentials object containing the username and password
* @param AxiosConfig The Axios config for the instance you wish to create * @param AxiosConfig The Axios config for the instance you wish to create
* *
* @returns This function returns an axios instance configured to use the provided credentials * @returns This function returns an axios instance configured to use the provided credentials
*/ */
function NtlmClient(credentials, AxiosConfig) { function NtlmClient(credentials, AxiosConfig) {
var _this = this; var _this = this;
var config = AxiosConfig !== null && AxiosConfig !== void 0 ? AxiosConfig : {}; var config = AxiosConfig !== null && AxiosConfig !== void 0 ? AxiosConfig : {};
@ -82,7 +198,8 @@ function NtlmClient(credentials, AxiosConfig) {
var client = axios_1.default.create(config); var client = axios_1.default.create(config);
client.interceptors.response.use(function (response) { client.interceptors.response.use(function (response) {
return response; return response;
}, function (err) { return __awaiter(_this, void 0, void 0, function () { }, function (err) {
return __awaiter(_this, void 0, void 0, function () {
var error, t1Msg, t2Msg, t3Msg, stream_1; var error, t1Msg, t2Msg, t3Msg, stream_1;
var _a; var _a;
return __generator(this, function (_b) { return __generator(this, function (_b) {
@ -90,38 +207,47 @@ function NtlmClient(credentials, AxiosConfig) {
case 0: case 0:
error = err.response; error = err.response;
if (!(error && error.status === 401 if (!(error && error.status === 401
&& error.headers['www-authenticate'] && error.headers["www-authenticate"]
&& error.headers['www-authenticate'].includes('NTLM'))) return [3 /*break*/, 3]; && error.headers["www-authenticate"].includes("NTLM"))) {
return [ 3 /*break*/, 3 ];
}
// This length check is a hack because SharePoint is awkward and will // This length check is a hack because SharePoint is awkward and will
// include the Negotiate option when responding with the T2 message // include the Negotiate option when responding with the T2 message
// There is nore we could do to ensure we are processing correctly, // There is nore we could do to ensure we are processing correctly,
// but this is the easiest option for now // but this is the easiest option for now
if (error.headers['www-authenticate'].length < 50) { if (error.headers["www-authenticate"].length < 50) {
t1Msg = ntlm.createType1Message(credentials.workstation, credentials.domain); t1Msg = ntlm.createType1Message(credentials.workstation, credentials.domain);
error.config.headers["Authorization"] = t1Msg; error.config.headers["Authorization"] = t1Msg;
} } else {
else { t2Msg = ntlm.decodeType2Message((error.headers["www-authenticate"].match(/^NTLM\s+(.+?)(,|\s+|$)/) || [])[1]);
t2Msg = ntlm.decodeType2Message((error.headers['www-authenticate'].match(/^NTLM\s+(.+?)(,|\s+|$)/) || [])[1]);
t3Msg = ntlm.createType3Message(t2Msg, credentials.username, credentials.password, credentials.workstation, credentials.domain); t3Msg = ntlm.createType3Message(t2Msg, credentials.username, credentials.password, credentials.workstation, credentials.domain);
error.config.headers["X-retry"] = "false"; error.config.headers["X-retry"] = "false";
error.config.headers["Authorization"] = t3Msg; error.config.headers["Authorization"] = t3Msg;
} }
if (!(error.config.responseType === "stream")) return [3 /*break*/, 2]; if (!(error.config.responseType === "stream")) {
return [ 3 /*break*/, 2 ];
}
stream_1 = (_a = err.response) === null || _a === void 0 ? void 0 : _a.data; stream_1 = (_a = err.response) === null || _a === void 0 ? void 0 : _a.data;
if (!(stream_1 && !stream_1.readableEnded)) return [3 /*break*/, 2]; if (!(stream_1 && !stream_1.readableEnded)) {
return [4 /*yield*/, new Promise(function (resolve) { return [ 3 /*break*/, 2 ];
}
return [ 4 /*yield*/, new Promise(function (resolve) {
stream_1.pipe((0, dev_null_1.default)()); stream_1.pipe((0, dev_null_1.default)());
stream_1.once('close', resolve); stream_1.once('close', resolve);
})]; }) ];
case 1: case 1:
_b.sent(); _b.sent();
_b.label = 2; _b.label = 2;
case 2: return [2 /*return*/, client(error.config)]; case 2:
case 3: throw err; return [ 2 /*return*/, client(error.config) ];
case 3:
throw err;
} }
}); });
}); }); });
});
return client; return client;
} }
exports.NtlmClient = NtlmClient; exports.NtlmClient = NtlmClient;
//# sourceMappingURL=ntlmClient.js.map //# sourceMappingURL=ntlmClient.js.map

View file

@ -1,20 +1,24 @@
import { PluginFunc, ConfigType } from 'dayjs' import { PluginFunc, ConfigType } from "dayjs";
declare const plugin: PluginFunc declare const plugin: PluginFunc;
export = plugin export = plugin
declare module 'dayjs' { declare module "dayjs" {
interface Dayjs { interface Dayjs {
tz(timezone?: string, keepLocalTime?: boolean): Dayjs tz(timezone?: string, keepLocalTime?: boolean): Dayjs;
offsetName(type?: 'short' | 'long'): string | undefined
offsetName(type?: "short" | "long"): string | undefined;
} }
interface DayjsTimezone { interface DayjsTimezone {
(date: ConfigType, timezone?: string): Dayjs (date: ConfigType, timezone?: string): Dayjs;
(date: ConfigType, format: string, timezone?: string): Dayjs
guess(): string (date: ConfigType, format: string, timezone?: string): Dayjs;
setDefault(timezone?: string): void
guess(): string;
setDefault(timezone?: string): void;
} }
const tz: DayjsTimezone const tz: DayjsTimezone;
} }

View file

@ -15,7 +15,7 @@
day: 2, day: 2,
hour: 3, hour: 3,
minute: 4, minute: 4,
second: 5 second: 5,
}; };
let e = {}; let e = {};
return function (n, i, o) { return function (n, i, o) {
@ -37,7 +37,7 @@
hour: "2-digit", hour: "2-digit",
minute: "2-digit", minute: "2-digit",
second: "2-digit", second: "2-digit",
timeZoneName: i timeZoneName: i,
}), e[o] = r), r; }), e[o] = r), r;
}(n, i); }(n, i);
return r.formatToParts(o); return r.formatToParts(o);

View file

@ -1,5 +1,8 @@
const { MonitorType } = require("./monitor-type"); const { MonitorType } = require("./monitor-type");
const { UP, DOWN } = require("../../src/util"); const {
UP,
DOWN,
} = require("../../src/util");
const dayjs = require("dayjs"); const dayjs = require("dayjs");
const { dnsResolve } = require("../util-server"); const { dnsResolve } = require("../util-server");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
@ -14,7 +17,7 @@ class DnsMonitorType extends MonitorType {
supportsConditions = true; supportsConditions = true;
conditionVariables = [ conditionVariables = [
new ConditionVariable("record", defaultStringOperators ), new ConditionVariable("record", defaultStringOperators),
]; ];
/** /**

View file

@ -1,5 +1,8 @@
const { MonitorType } = require("./monitor-type"); const { MonitorType } = require("./monitor-type");
const { log, UP } = require("../../src/util"); const {
log,
UP,
} = require("../../src/util");
const mqtt = require("mqtt"); const mqtt = require("mqtt");
const jsonata = require("jsonata"); const jsonata = require("jsonata");
@ -57,7 +60,12 @@ class MqttMonitorType extends MonitorType {
*/ */
mqttAsync(hostname, topic, options = {}) { mqttAsync(hostname, topic, options = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const { port, username, password, interval = 20 } = options; const {
port,
username,
password,
interval = 20,
} = options;
// Adds MQTT protocol to the hostname if not already present // Adds MQTT protocol to the hostname if not already present
if (!/^(?:http|mqtt|ws)s?:\/\//.test(hostname)) { if (!/^(?:http|mqtt|ws)s?:\/\//.test(hostname)) {
@ -77,7 +85,7 @@ class MqttMonitorType extends MonitorType {
let client = mqtt.connect(mqttUrl, { let client = mqtt.connect(mqttUrl, {
username, username,
password, password,
clientId: "uptime-kuma_" + Math.random().toString(16).substr(2, 8) clientId: "uptime-kuma_" + Math.random().toString(16).substr(2, 8),
}); });
client.on("connect", () => { client.on("connect", () => {

View file

@ -1,5 +1,9 @@
const { MonitorType } = require("./monitor-type"); const { MonitorType } = require("./monitor-type");
const { log, UP, DOWN } = require("../../src/util"); const {
log,
UP,
DOWN,
} = require("../../src/util");
const { axiosAbortSignal } = require("../util-server"); const { axiosAbortSignal } = require("../util-server");
const axios = require("axios"); const axios = require("axios");
@ -21,7 +25,7 @@ class RabbitMqMonitorType extends MonitorType {
for (let baseUrl of baseUrls) { for (let baseUrl of baseUrls) {
try { try {
// Without a trailing slash, path in baseUrl will be removed. https://example.com/api -> https://example.com // Without a trailing slash, path in baseUrl will be removed. https://example.com/api -> https://example.com
if ( !baseUrl.endsWith("/") ) { if (!baseUrl.endsWith("/")) {
baseUrl += "/"; baseUrl += "/";
} }
const options = { const options = {

View file

@ -1,6 +1,9 @@
const { MonitorType } = require("./monitor-type"); const { MonitorType } = require("./monitor-type");
const { chromium } = require("playwright-core"); const { chromium } = require("playwright-core");
const { UP, log } = require("../../src/util"); const {
UP,
log,
} = require("../../src/util");
const { Settings } = require("../settings"); const { Settings } = require("../settings");
const commandExistsSync = require("command-exists").sync; const commandExistsSync = require("command-exists").sync;
const childProcess = require("child_process"); const childProcess = require("child_process");
@ -122,7 +125,7 @@ async function prepareChromeExecutable(executablePath) {
executablePath = "/usr/bin/chromium"; executablePath = "/usr/bin/chromium";
// Install chromium in container via apt install // Install chromium in container via apt install
if ( !commandExistsSync(executablePath)) { if (!commandExistsSync(executablePath)) {
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
log.info("Chromium", "Installing Chromium..."); log.info("Chromium", "Installing Chromium...");
let child = childProcess.exec("apt update && apt --yes --no-install-recommends install chromium fonts-indic fonts-noto fonts-noto-cjk"); let child = childProcess.exec("apt update && apt --yes --no-install-recommends install chromium fonts-indic fonts-noto fonts-noto-cjk");
@ -213,6 +216,7 @@ async function testChrome(executablePath) {
throw new Error(e.message); throw new Error(e.message);
} }
} }
// test remote browser // test remote browser
/** /**
* @param {string} remoteBrowserURL Remote Browser URL * @param {string} remoteBrowserURL Remote Browser URL
@ -228,6 +232,7 @@ async function testRemoteBrowser(remoteBrowserURL) {
throw new Error(e.message); throw new Error(e.message);
} }
} }
class RealBrowserMonitorType extends MonitorType { class RealBrowserMonitorType extends MonitorType {
name = "real-browser"; name = "real-browser";

View file

@ -1,5 +1,9 @@
const { MonitorType } = require("./monitor-type"); const { MonitorType } = require("./monitor-type");
const { UP, log, evaluateJsonQuery } = require("../../src/util"); const {
UP,
log,
evaluateJsonQuery,
} = require("../../src/util");
const snmp = require("net-snmp"); const snmp = require("net-snmp");
class SNMPMonitorType extends MonitorType { class SNMPMonitorType extends MonitorType {
@ -42,7 +46,10 @@ class SNMPMonitorType extends MonitorType {
// We restrict querying to one OID per monitor, therefore `varbinds[0]` will always contain the value we're interested in. // We restrict querying to one OID per monitor, therefore `varbinds[0]` will always contain the value we're interested in.
const value = varbinds[0].value; const value = varbinds[0].value;
const { status, response } = await evaluateJsonQuery(value, monitor.jsonPath, monitor.jsonPathOperator, monitor.expectedValue); const {
status,
response,
} = await evaluateJsonQuery(value, monitor.jsonPath, monitor.jsonPathOperator, monitor.expectedValue);
if (status) { if (status) {
heartbeat.status = UP; heartbeat.status = UP;

View file

@ -0,0 +1,81 @@
const NotificationProvider = require("./notification-provider");
const { DOWN } = require("../../src/util");
const NotifyClient = require("notifications-node-client").NotifyClient;
class GovNotify extends NotificationProvider {
name = "GovNotify";
/**
* Sends notifications via email and SMS using the GOV.UK Notify service.
* @param {object} notification The notification object containing configuration such as API key, email recipients, SMS recipients, message template, and template IDs for email and SMS.
* @param {string} msg The message content to send if no message template is provided in the notification object.
* @param {object | null} monitorJSON Optional parameter containing monitoring-related data.
* @param {object | null} heartbeatJSON Optional parameter containing heartbeat-related data, used to determine notification subject (e.g., status up or down).
* @returns {Promise<string>} A promise that resolves to a success message after sending notifications or rejects with an error if the sending fails.
* @throws {Error} Throws an error if notification sending fails.
*/
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
try {
const apiKey = notification.apiKey;
const emailRecipients = (typeof notification.emailRecipients === "string" && notification.emailRecipients.trim())
? notification.emailRecipients.split(",").map(e => e.trim()).filter(e => e)
: [];
const smsRecipients = (typeof notification.smsRecipients === "string" && notification.smsRecipients.trim())
? notification.smsRecipients.split(",").map(n => n.trim()).filter(n => n)
: [];
let message = notification.messageTemplate || msg;
const emailTemplateID = notification.emailTemplateId;
const smsTemplateID = notification.smsTemplateId;
const notifyClient = new NotifyClient(apiKey);
let subject = "⚠️ Test";
if (heartbeatJSON !== null) {
subject = (heartbeatJSON["status"] === DOWN) ? "🔴 Down" : "✅ Up";
}
const date = new Date();
const day = date.getDate();
const month = date.getMonth() + 1;
const year = date.getFullYear();
const hours = date.getHours();
const minutes = date.getMinutes();
const readableDate = `GMT ${day}/${month}/${year} ${hours}:${minutes}`;
message += `\n${readableDate}`;
// Send Emails
for (const email of emailRecipients) {
await notifyClient.sendEmail(
emailTemplateID,
email,
{
personalisation: {
message,
subject,
},
reference: "Uptime-Kuma"
});
}
// Send SMS
for (const number of smsRecipients) {
await notifyClient.sendSms(
smsTemplateID,
number,
{
personalisation: { message },
reference: "Uptime-Kuma"
});
}
return "Notification sent successfully";
} catch (error) {
console.error("GovNotify Error:", error.response ? error.response.data : error.message);
throw new Error("Failed to send notification via GOV Notify");
}
}
}
module.exports = GovNotify;

View file

@ -20,19 +20,21 @@ class HomeAssistant extends NotificationProvider {
{ {
title: "Uptime Kuma", title: "Uptime Kuma",
message: msg, message: msg,
...(notificationService !== "persistent_notification" && { data: { ...(notificationService !== "persistent_notification" && {
data: {
name: monitorJSON?.name, name: monitorJSON?.name,
status: heartbeatJSON?.status, status: heartbeatJSON?.status,
channel: "Uptime Kuma", channel: "Uptime Kuma",
icon_url: "https://github.com/louislam/uptime-kuma/blob/master/public/icon.png?raw=true", icon_url: "https://github.com/louislam/uptime-kuma/blob/master/public/icon.png?raw=true",
} }), },
}),
}, },
{ {
headers: { headers: {
Authorization: `Bearer ${notification.longLivedAccessToken}`, Authorization: `Bearer ${notification.longLivedAccessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
} },
); );
return okMsg; return okMsg;

View file

@ -69,6 +69,7 @@ const Cellsynt = require("./notification-providers/cellsynt");
const Onesender = require("./notification-providers/onesender"); const Onesender = require("./notification-providers/onesender");
const Wpush = require("./notification-providers/wpush"); const Wpush = require("./notification-providers/wpush");
const SendGrid = require("./notification-providers/send-grid"); const SendGrid = require("./notification-providers/send-grid");
const GovNotify = require("./notification-providers/gov-notify");
class Notification { class Notification {
@ -154,10 +155,11 @@ class Notification {
new GtxMessaging(), new GtxMessaging(),
new Cellsynt(), new Cellsynt(),
new Wpush(), new Wpush(),
new SendGrid() new SendGrid(),
new GovNotify(),
]; ];
for (let item of list) { for (let item of list) {
if (! item.name) { if (!item.name) {
throw new Error("Notification provider without name"); throw new Error("Notification provider without name");
} }
@ -181,7 +183,7 @@ class Notification {
if (this.providerList[notification.type]) { if (this.providerList[notification.type]) {
return this.providerList[notification.type].send(notification, msg, monitorJSON, heartbeatJSON); return this.providerList[notification.type].send(notification, msg, monitorJSON, heartbeatJSON);
} else { } else {
throw new Error("Notification type is not supported"); throw new Error(`Notification type <${notification.type}> is not supported`);
} }
} }
@ -201,7 +203,7 @@ class Notification {
userID, userID,
]); ]);
if (! bean) { if (!bean) {
throw new Error("notification not found"); throw new Error("notification not found");
} }
@ -234,7 +236,7 @@ class Notification {
userID, userID,
]); ]);
if (! bean) { if (!bean) {
throw new Error("notification not found"); throw new Error("notification not found");
} }
@ -261,7 +263,7 @@ class Notification {
*/ */
async function applyNotificationEveryMonitor(notificationID, userID) { async function applyNotificationEveryMonitor(notificationID, userID) {
let monitors = await R.getAll("SELECT id FROM monitor WHERE user_id = ?", [ let monitors = await R.getAll("SELECT id FROM monitor WHERE user_id = ?", [
userID userID,
]); ]);
for (let i = 0; i < monitors.length; i++) { for (let i = 0; i < monitors.length; i++) {
@ -270,7 +272,7 @@ async function applyNotificationEveryMonitor(notificationID, userID) {
notificationID, notificationID,
]); ]);
if (! checkNotification) { if (!checkNotification) {
let relation = R.dispense("monitor_notification"); let relation = R.dispense("monitor_notification");
relation.monitor_id = monitors[i].id; relation.monitor_id = monitors[i].id;
relation.notification_id = notificationID; relation.notification_id = notificationID;

View file

@ -12,24 +12,24 @@ const commonLabels = [
const monitorCertDaysRemaining = new PrometheusClient.Gauge({ const monitorCertDaysRemaining = new PrometheusClient.Gauge({
name: "monitor_cert_days_remaining", name: "monitor_cert_days_remaining",
help: "The number of days remaining until the certificate expires", help: "The number of days remaining until the certificate expires",
labelNames: commonLabels labelNames: commonLabels,
}); });
const monitorCertIsValid = new PrometheusClient.Gauge({ const monitorCertIsValid = new PrometheusClient.Gauge({
name: "monitor_cert_is_valid", name: "monitor_cert_is_valid",
help: "Is the certificate still valid? (1 = Yes, 0= No)", help: "Is the certificate still valid? (1 = Yes, 0= No)",
labelNames: commonLabels labelNames: commonLabels,
}); });
const monitorResponseTime = new PrometheusClient.Gauge({ const monitorResponseTime = new PrometheusClient.Gauge({
name: "monitor_response_time", name: "monitor_response_time",
help: "Monitor Response Time (ms)", help: "Monitor Response Time (ms)",
labelNames: commonLabels labelNames: commonLabels,
}); });
const monitorStatus = new PrometheusClient.Gauge({ const monitorStatus = new PrometheusClient.Gauge({
name: "monitor_status", name: "monitor_status",
help: "Monitor Status (1 = UP, 0= DOWN, 2= PENDING, 3= MAINTENANCE)", help: "Monitor Status (1 = UP, 0= DOWN, 2= PENDING, 3= MAINTENANCE)",
labelNames: commonLabels labelNames: commonLabels,
}); });
class Prometheus { class Prometheus {
@ -44,7 +44,7 @@ class Prometheus {
monitor_type: monitor.type, monitor_type: monitor.type,
monitor_url: monitor.url, monitor_url: monitor.url,
monitor_hostname: monitor.hostname, monitor_hostname: monitor.hostname,
monitor_port: monitor.port monitor_port: monitor.port,
}; };
} }
@ -119,5 +119,5 @@ class Prometheus {
} }
module.exports = { module.exports = {
Prometheus Prometheus,
}; };

View file

@ -36,7 +36,7 @@ class Proxy {
if (!this.SUPPORTED_PROXY_PROTOCOLS.includes(proxy.protocol)) { if (!this.SUPPORTED_PROXY_PROTOCOLS.includes(proxy.protocol)) {
throw new Error(` throw new Error(`
Unsupported proxy protocol "${proxy.protocol}. Unsupported proxy protocol "${proxy.protocol}.
Supported protocols are ${this.SUPPORTED_PROXY_PROTOCOLS.join(", ")}."` Supported protocols are ${this.SUPPORTED_PROXY_PROTOCOLS.join(", ")}."`,
); );
} }
@ -92,7 +92,10 @@ class Proxy {
* @throws Proxy protocol is unsupported * @throws Proxy protocol is unsupported
*/ */
static createAgents(proxy, options) { static createAgents(proxy, options) {
const { httpAgentOptions, httpsAgentOptions } = options || {}; const {
httpAgentOptions,
httpsAgentOptions,
} = options || {};
let agent; let agent;
let httpAgent; let httpAgent;
let httpsAgent; let httpsAgent;
@ -150,12 +153,13 @@ class Proxy {
httpsAgent = agent; httpsAgent = agent;
break; break;
default: throw new Error(`Unsupported proxy protocol provided. ${proxy.protocol}`); default:
throw new Error(`Unsupported proxy protocol provided. ${proxy.protocol}`);
} }
return { return {
httpAgent, httpAgent,
httpsAgent httpsAgent,
}; };
} }

View file

@ -11,7 +11,15 @@ const { R } = require("redbean-node");
const apicache = require("../modules/apicache"); const apicache = require("../modules/apicache");
const Monitor = require("../model/monitor"); const Monitor = require("../model/monitor");
const dayjs = require("dayjs"); const dayjs = require("dayjs");
const { UP, MAINTENANCE, DOWN, PENDING, flipStatus, log, badgeConstants } = require("../../src/util"); const {
UP,
MAINTENANCE,
DOWN,
PENDING,
flipStatus,
log,
badgeConstants,
} = require("../../src/util");
const StatusPage = require("../model/status_page"); const StatusPage = require("../model/status_page");
const { UptimeKumaServer } = require("../uptime-kuma-server"); const { UptimeKumaServer } = require("../uptime-kuma-server");
const { makeBadge } = require("badge-maker"); const { makeBadge } = require("badge-maker");
@ -28,7 +36,7 @@ let io = server.io;
router.get("/api/entry-page", async (request, response) => { router.get("/api/entry-page", async (request, response) => {
allowDevAllOrigin(response); allowDevAllOrigin(response);
let result = { }; let result = {};
let hostname = request.hostname; let hostname = request.hostname;
if ((await setting("trustProxy")) && request.headers["x-forwarded-host"]) { if ((await setting("trustProxy")) && request.headers["x-forwarded-host"]) {
hostname = request.headers["x-forwarded-host"]; hostname = request.headers["x-forwarded-host"];
@ -53,10 +61,10 @@ router.all("/api/push/:pushToken", async (request, response) => {
let status = (statusString === "up") ? UP : DOWN; let status = (statusString === "up") ? UP : DOWN;
let monitor = await R.findOne("monitor", " push_token = ? AND active = 1 ", [ let monitor = await R.findOne("monitor", " push_token = ? AND active = 1 ", [
pushToken pushToken,
]); ]);
if (! monitor) { if (!monitor) {
throw new Error("Monitor not found or not active."); throw new Error("Monitor not found or not active.");
} }
@ -127,7 +135,7 @@ router.all("/api/push/:pushToken", async (request, response) => {
} catch (e) { } catch (e) {
response.status(404).json({ response.status(404).json({
ok: false, ok: false,
msg: e.message msg: e.message,
}); });
} }
}); });
@ -159,7 +167,7 @@ router.get("/api/badge/:id/status", cache("5 minutes"), async (request, response
AND monitor_group.monitor_id = ? AND monitor_group.monitor_id = ?
AND public = 1 AND public = 1
`, `,
[ requestedMonitorId ] [ requestedMonitorId ],
); );
const badgeValues = { style }; const badgeValues = { style };
@ -242,7 +250,7 @@ router.get("/api/badge/:id/uptime/:duration?", cache("5 minutes"), async (reques
AND monitor_group.monitor_id = ? AND monitor_group.monitor_id = ?
AND public = 1 AND public = 1
`, `,
[ requestedMonitorId ] [ requestedMonitorId ],
); );
const badgeValues = { style }; const badgeValues = { style };
@ -362,7 +370,7 @@ router.get("/api/badge/:id/avg-response/:duration?", cache("5 minutes"), async (
request.params.duration request.params.duration
? parseInt(request.params.duration, 10) ? parseInt(request.params.duration, 10)
: 24, : 24,
720 720,
); );
const overrideValue = value && parseFloat(value); const overrideValue = value && parseFloat(value);
@ -376,7 +384,7 @@ router.get("/api/badge/:id/avg-response/:duration?", cache("5 minutes"), async (
AND public = 1 AND public = 1
AND heartbeat.monitor_id = ? AND heartbeat.monitor_id = ?
`, `,
[ -requestedDuration, requestedMonitorId ] [ -requestedDuration, requestedMonitorId ],
)); ));
const badgeValues = { style }; const badgeValues = { style };
@ -443,7 +451,7 @@ router.get("/api/badge/:id/cert-exp", cache("5 minutes"), async (request, respon
AND monitor_group.monitor_id = ? AND monitor_group.monitor_id = ?
AND public = 1 AND public = 1
`, `,
[ requestedMonitorId ] [ requestedMonitorId ],
); );
const badgeValues = { style }; const badgeValues = { style };
@ -528,7 +536,7 @@ router.get("/api/badge/:id/response", cache("5 minutes"), async (request, respon
AND monitor_group.monitor_id = ? AND monitor_group.monitor_id = ?
AND public = 1 AND public = 1
`, `,
[ requestedMonitorId ] [ requestedMonitorId ],
); );
const badgeValues = { style }; const badgeValues = { style };
@ -540,7 +548,7 @@ router.get("/api/badge/:id/response", cache("5 minutes"), async (request, respon
badgeValues.color = badgeConstants.naColor; badgeValues.color = badgeConstants.naColor;
} else { } else {
const heartbeat = await Monitor.getPreviousHeartbeat( const heartbeat = await Monitor.getPreviousHeartbeat(
requestedMonitorId requestedMonitorId,
); );
if (!heartbeat.ping) { if (!heartbeat.ping) {

View file

@ -2,7 +2,10 @@ let express = require("express");
const apicache = require("../modules/apicache"); const apicache = require("../modules/apicache");
const { UptimeKumaServer } = require("../uptime-kuma-server"); const { UptimeKumaServer } = require("../uptime-kuma-server");
const StatusPage = require("../model/status_page"); const StatusPage = require("../model/status_page");
const { allowDevAllOrigin, sendHttpError } = require("../util-server"); const {
allowDevAllOrigin,
sendHttpError,
} = require("../util-server");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const { badgeConstants } = require("../../src/util"); const { badgeConstants } = require("../../src/util");
const { makeBadge } = require("badge-maker"); const { makeBadge } = require("badge-maker");
@ -44,7 +47,7 @@ router.get("/api/status-page/:slug", cache("5 minutes"), async (request, respons
try { try {
// Get Status Page // Get Status Page
let statusPage = await R.findOne("status_page", " slug = ? ", [ let statusPage = await R.findOne("status_page", " slug = ? ", [
slug slug,
]); ]);
if (!statusPage) { if (!statusPage) {
@ -81,7 +84,7 @@ router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (reques
AND public = 1 AND public = 1
AND \`group\`.status_page_id = ? AND \`group\`.status_page_id = ?
`, [ `, [
statusPageID statusPageID,
]); ]);
for (let monitorID of monitorIDList) { for (let monitorID of monitorIDList) {
@ -103,7 +106,7 @@ router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (reques
response.json({ response.json({
heartbeatList, heartbeatList,
uptimeList uptimeList,
}); });
} catch (error) { } catch (error) {
@ -120,7 +123,7 @@ router.get("/api/status-page/:slug/manifest.json", cache("1440 minutes"), async
try { try {
// Get Status Page // Get Status Page
let statusPage = await R.findOne("status_page", " slug = ? ", [ let statusPage = await R.findOne("status_page", " slug = ? ", [
slug slug,
]); ]);
if (!statusPage) { if (!statusPage) {
@ -137,9 +140,9 @@ router.get("/api/status-page/:slug/manifest.json", cache("1440 minutes"), async
{ {
"src": statusPage.icon, "src": statusPage.icon,
"sizes": "128x128", "sizes": "128x128",
"type": "image/png" "type": "image/png",
} },
] ],
}); });
} catch (error) { } catch (error) {
@ -159,7 +162,7 @@ router.get("/api/status-page/:slug/badge", cache("5 minutes"), async (request, r
downColor = badgeConstants.defaultDownColor, downColor = badgeConstants.defaultDownColor,
partialColor = "#F6BE00", partialColor = "#F6BE00",
maintenanceColor = "#808080", maintenanceColor = "#808080",
style = badgeConstants.defaultStyle style = badgeConstants.defaultStyle,
} = request.query; } = request.query;
try { try {
@ -169,7 +172,7 @@ router.get("/api/status-page/:slug/badge", cache("5 minutes"), async (request, r
AND public = 1 AND public = 1
AND \`group\`.status_page_id = ? AND \`group\`.status_page_id = ?
`, [ `, [
statusPageID statusPageID,
]); ]);
let hasUp = false; let hasUp = false;

View file

@ -37,13 +37,19 @@ if (!semver.satisfies(nodeVersion, requiredNodeVersions)) {
} }
const args = require("args-parser")(process.argv); const args = require("args-parser")(process.argv);
const { sleep, log, getRandomInt, genSecret, isDev } = require("../src/util"); const {
sleep,
log,
getRandomInt,
genSecret,
isDev,
} = require("../src/util");
const config = require("./config"); const config = require("./config");
log.debug("server", "Arguments"); log.debug("server", "Arguments");
log.debug("server", args); log.debug("server", args);
if (! process.env.NODE_ENV) { if (!process.env.NODE_ENV) {
process.env.NODE_ENV = "production"; process.env.NODE_ENV = "production";
} }
@ -90,7 +96,16 @@ const Monitor = require("./model/monitor");
const User = require("./model/user"); const User = require("./model/user");
log.debug("server", "Importing Settings"); log.debug("server", "Importing Settings");
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, doubleCheckPassword, shake256, SHAKE256_LENGTH, allowDevAllOrigin, const {
getSettings,
setSettings,
setting,
initJWTSecret,
checkLogin,
doubleCheckPassword,
shake256,
SHAKE256_LENGTH,
allowDevAllOrigin,
} = require("./util-server"); } = require("./util-server");
log.debug("server", "Importing Notification"); log.debug("server", "Importing Notification");
@ -101,8 +116,14 @@ log.debug("server", "Importing Database");
const Database = require("./database"); const Database = require("./database");
log.debug("server", "Importing Background Jobs"); log.debug("server", "Importing Background Jobs");
const { initBackgroundJobs, stopBackgroundJobs } = require("./jobs"); const {
const { loginRateLimiter, twoFaRateLimiter } = require("./rate-limiter"); initBackgroundJobs,
stopBackgroundJobs,
} = require("./jobs");
const {
loginRateLimiter,
twoFaRateLimiter,
} = require("./rate-limiter");
const { apiAuth } = require("./auth"); const { apiAuth } = require("./auth");
const { login } = require("./auth"); const { login } = require("./auth");
@ -122,7 +143,7 @@ const cloudflaredToken = args["cloudflared-token"] || process.env.UPTIME_KUMA_CL
// 2FA / notp verification defaults // 2FA / notp verification defaults
const twoFAVerifyOptions = { const twoFAVerifyOptions = {
"window": 1, "window": 1,
"time": 30 "time": 30,
}; };
/** /**
@ -132,13 +153,26 @@ const twoFAVerifyOptions = {
const testMode = !!args["test"] || false; const testMode = !!args["test"] || false;
// Must be after io instantiation // Must be after io instantiation
const { sendNotificationList, sendHeartbeatList, sendInfo, sendProxyList, sendDockerHostList, sendAPIKeyList, sendRemoteBrowserList, sendMonitorTypeList } = require("./client"); const {
sendNotificationList,
sendHeartbeatList,
sendInfo,
sendProxyList,
sendDockerHostList,
sendAPIKeyList,
sendRemoteBrowserList,
sendMonitorTypeList,
} = require("./client");
const { statusPageSocketHandler } = require("./socket-handlers/status-page-socket-handler"); const { statusPageSocketHandler } = require("./socket-handlers/status-page-socket-handler");
const { databaseSocketHandler } = require("./socket-handlers/database-socket-handler"); const { databaseSocketHandler } = require("./socket-handlers/database-socket-handler");
const { remoteBrowserSocketHandler } = require("./socket-handlers/remote-browser-socket-handler"); const { remoteBrowserSocketHandler } = require("./socket-handlers/remote-browser-socket-handler");
const TwoFA = require("./2fa"); const TwoFA = require("./2fa");
const StatusPage = require("./model/status_page"); const StatusPage = require("./model/status_page");
const { cloudflaredSocketHandler, autoStart: cloudflaredAutoStart, stop: cloudflaredStop } = require("./socket-handlers/cloudflared-socket-handler"); const {
cloudflaredSocketHandler,
autoStart: cloudflaredAutoStart,
stop: cloudflaredStop,
} = require("./socket-handlers/cloudflared-socket-handler");
const { proxySocketHandler } = require("./socket-handlers/proxy-socket-handler"); const { proxySocketHandler } = require("./socket-handlers/proxy-socket-handler");
const { dockerSocketHandler } = require("./socket-handlers/docker-socket-handler"); const { dockerSocketHandler } = require("./socket-handlers/docker-socket-handler");
const { maintenanceSocketHandler } = require("./socket-handlers/maintenance-socket-handler"); const { maintenanceSocketHandler } = require("./socket-handlers/maintenance-socket-handler");
@ -933,8 +967,9 @@ let needSetup = false;
monitorID, monitorID,
socket.userID, socket.userID,
]); ]);
const monitorData = [{ id: monitor.id, const monitorData = [{
active: monitor.active id: monitor.id,
active: monitor.active,
}]; }];
const preloadData = await Monitor.preparePreloadData(monitorData); const preloadData = await Monitor.preparePreloadData(monitorData);
callback({ callback({
@ -966,7 +1001,8 @@ let needSetup = false;
SELECT * SELECT *
FROM heartbeat FROM heartbeat
WHERE monitor_id = ? WHERE monitor_id = ?
AND time > ${sqlHourOffset} AND time
> ${sqlHourOffset}
ORDER BY time ASC ORDER BY time ASC
`, [ `, [
monitorID, monitorID,
@ -1519,7 +1555,7 @@ let needSetup = false;
log.info("manage", `Clear Heartbeats Monitor: ${monitorID} User ID: ${socket.userID}`); log.info("manage", `Clear Heartbeats Monitor: ${monitorID} User ID: ${socket.userID}`);
await R.exec("DELETE FROM heartbeat WHERE monitor_id = ?", [ await R.exec("DELETE FROM heartbeat WHERE monitor_id = ?", [
monitorID monitorID,
]); ]);
await sendHeartbeatList(socket, monitorID, true, true); await sendHeartbeatList(socket, monitorID, true, true);
@ -1658,7 +1694,7 @@ async function checkOwner(userID, monitorID) {
userID, userID,
]); ]);
if (! row) { if (!row) {
throw new Error("You do not own this monitor."); throw new Error("You do not own this monitor.");
} }
} }
@ -1698,7 +1734,7 @@ async function afterLogin(socket, user) {
// Set server timezone from client browser if not set // Set server timezone from client browser if not set
// It should be run once only // It should be run once only
if (! await Settings.get("initServerTimezone")) { if (!await Settings.get("initServerTimezone")) {
log.debug("server", "emit initServerTimezone"); log.debug("server", "emit initServerTimezone");
socket.emit("initServerTimezone"); socket.emit("initServerTimezone");
} }
@ -1722,7 +1758,7 @@ async function initDatabase(testMode = false) {
"jwtSecret", "jwtSecret",
]); ]);
if (! jwtSecretBean) { if (!jwtSecretBean) {
log.info("server", "JWT secret is not found, generate one."); log.info("server", "JWT secret is not found, generate one.");
jwtSecretBean = await initJWTSecret(); jwtSecretBean = await initJWTSecret();
log.info("server", "Stored JWT secret into database"); log.info("server", "Stored JWT secret into database");

View file

@ -17,9 +17,7 @@ class Settings {
* } * }
* @type {{}} * @type {{}}
*/ */
static cacheList = { static cacheList = {};
};
static cacheCleaner = null; static cacheCleaner = null;
@ -61,7 +59,7 @@ class Settings {
Settings.cacheList[key] = { Settings.cacheList[key] = {
value: v, value: v,
timestamp: Date.now() timestamp: Date.now(),
}; };
return v; return v;
@ -129,7 +127,7 @@ class Settings {
for (let key of keyList) { for (let key of keyList) {
let bean = await R.findOne("setting", " `key` = ? ", [ let bean = await R.findOne("setting", " `key` = ? ", [
key key,
]); ]);
if (bean == null) { if (bean == null) {

View file

@ -1,7 +1,11 @@
const tcpp = require("tcp-ping"); const tcpp = require("tcp-ping");
const ping = require("@louislam/ping"); const ping = require("@louislam/ping");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const { log, genSecret, badgeConstants } = require("../src/util"); const {
log,
genSecret,
badgeConstants,
} = require("../src/util");
const passwordHash = require("./password-hash"); const passwordHash = require("./password-hash");
const { Resolver } = require("dns"); const { Resolver } = require("dns");
const iconv = require("iconv-lite"); const iconv = require("iconv-lite");
@ -22,14 +26,20 @@ const tls = require("tls");
const { const {
dictionaries: { dictionaries: {
rfc2865: { file, attributes }, rfc2865: {
file,
attributes,
},
}, },
} = require("node-radius-utils"); } = require("node-radius-utils");
const dayjs = require("dayjs"); const dayjs = require("dayjs");
// SASLOptions used in JSDoc // SASLOptions used in JSDoc
// eslint-disable-next-line no-unused-vars const {
const { Kafka, SASLOptions } = require("kafkajs"); Kafka,
// eslint-disable-next-line no-unused-vars
SASLOptions,
} = require("kafkajs");
const crypto = require("crypto"); const crypto = require("crypto");
const isWindows = process.platform === /^win/.test(process.platform); const isWindows = process.platform === /^win/.test(process.platform);
@ -75,7 +85,7 @@ exports.getOidcTokenClientCredentials = async (tokenEndpoint, clientId, clientSe
let client = new oauthProvider.Client({ let client = new oauthProvider.Client({
client_id: clientId, client_id: clientId,
client_secret: clientSecret, client_secret: clientSecret,
token_endpoint_auth_method: authMethod token_endpoint_auth_method: authMethod,
}); });
// Increase default timeout and clock tolerance // Increase default timeout and clock tolerance
@ -185,7 +195,12 @@ exports.pingAsync = function (hostname, ipv6 = false, size = 56) {
*/ */
exports.kafkaProducerAsync = function (brokers, topic, message, options = {}, saslOptions = {}) { exports.kafkaProducerAsync = function (brokers, topic, message, options = {}, saslOptions = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const { interval = 20, allowAutoTopicCreation = false, ssl = false, clientId = "Uptime-Kuma" } = options; const {
interval = 20,
allowAutoTopicCreation = false,
ssl = false,
clientId = "Uptime-Kuma",
} = options;
let connectedToKafka = false; let connectedToKafka = false;
@ -213,7 +228,7 @@ exports.kafkaProducerAsync = function (brokers, topic, message, options = {}, sa
allowAutoTopicCreation: allowAutoTopicCreation, allowAutoTopicCreation: allowAutoTopicCreation,
retry: { retry: {
retries: 0, retries: 0,
} },
}); });
producer.connect().then( producer.connect().then(
@ -234,14 +249,14 @@ exports.kafkaProducerAsync = function (brokers, topic, message, options = {}, sa
connectedToKafka = true; connectedToKafka = true;
clearTimeout(timeoutID); clearTimeout(timeoutID);
}); });
} },
).catch( ).catch(
(e) => { (e) => {
connectedToKafka = true; connectedToKafka = true;
producer.disconnect(); producer.disconnect();
clearTimeout(timeoutID); clearTimeout(timeoutID);
reject(new Error("Error in producer connection: " + e.message)); reject(new Error("Error in producer connection: " + e.message));
} },
); );
producer.on("producer.network.request_timeout", (_) => { producer.on("producer.network.request_timeout", (_) => {
@ -409,7 +424,7 @@ exports.mysqlQuery = function (connectionString, query, password = undefined) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const connection = mysql.createConnection({ const connection = mysql.createConnection({
uri: connectionString, uri: connectionString,
password password,
}); });
connection.on("error", (err) => { connection.on("error", (err) => {
@ -494,8 +509,8 @@ exports.redisPingAsync = function (dsn, rejectUnauthorized) {
const client = redis.createClient({ const client = redis.createClient({
url: dsn, url: dsn,
socket: { socket: {
rejectUnauthorized rejectUnauthorized,
} },
}); });
client.on("error", (err) => { client.on("error", (err) => {
if (client.isOpen) { if (client.isOpen) {
@ -661,7 +676,7 @@ exports.checkCertificate = function (socket) {
return { return {
valid: valid, valid: valid,
certInfo: parsedInfo certInfo: parsedInfo,
}; };
}; };
@ -693,7 +708,7 @@ exports.checkStatusCode = function (status, acceptedCodes) {
} }
} else { } else {
log.error("monitor", `${codeRange} is not a valid status code range`); log.error("monitor", `${codeRange} is not a valid status code range`);
continue;
} }
} }
@ -925,14 +940,21 @@ module.exports.timeObjectToLocal = (obj, timezone = undefined) => {
* @returns {Promise<object>} Result of gRPC query * @returns {Promise<object>} Result of gRPC query
*/ */
module.exports.grpcQuery = async (options) => { module.exports.grpcQuery = async (options) => {
const { grpcUrl, grpcProtobufData, grpcServiceName, grpcEnableTls, grpcMethod, grpcBody } = options; const {
grpcUrl,
grpcProtobufData,
grpcServiceName,
grpcEnableTls,
grpcMethod,
grpcBody,
} = options;
const protocObject = protojs.parse(grpcProtobufData); const protocObject = protojs.parse(grpcProtobufData);
const protoServiceObject = protocObject.root.lookupService(grpcServiceName); const protoServiceObject = protocObject.root.lookupService(grpcServiceName);
const Client = grpc.makeGenericClientConstructor({}); const Client = grpc.makeGenericClientConstructor({});
const credentials = grpcEnableTls ? grpc.credentials.createSsl() : grpc.credentials.createInsecure(); const credentials = grpcEnableTls ? grpc.credentials.createSsl() : grpc.credentials.createInsecure();
const client = new Client( const client = new Client(
grpcUrl, grpcUrl,
credentials credentials,
); );
const grpcService = protoServiceObject.create(function (method, requestData, cb) { const grpcService = protoServiceObject.create(function (method, requestData, cb) {
const fullServiceName = method.fullName; const fullServiceName = method.fullName;
@ -955,14 +977,14 @@ module.exports.grpcQuery = async (options) => {
return resolve({ return resolve({
code: err.code, code: err.code,
errorMessage: err.details, errorMessage: err.details,
data: "" data: "",
}); });
} else { } else {
log.debug("monitor:", `gRPC response: ${JSON.stringify(response)}`); log.debug("monitor:", `gRPC response: ${JSON.stringify(response)}`);
return resolve({ return resolve({
code: 1, code: 1,
errorMessage: "", errorMessage: "",
data: responseData data: responseData,
}); });
} }
}); });
@ -970,7 +992,7 @@ module.exports.grpcQuery = async (options) => {
return resolve({ return resolve({
code: -1, code: -1,
errorMessage: `Error ${err}. Please review your gRPC configuration option. The service name must not include package name value, and the method name must follow camelCase format`, errorMessage: `Error ${err}. Please review your gRPC configuration option. The service name must not include package name value, and the method name must follow camelCase format`,
data: "" data: "",
}); });
} }

View file

@ -81,5 +81,5 @@ class ArrayWithKey {
} }
module.exports = { module.exports = {
ArrayWithKey ArrayWithKey,
}; };

View file

@ -44,5 +44,5 @@ class LimitQueue extends ArrayWithKey {
} }
module.exports = { module.exports = {
LimitQueue LimitQueue,
}; };

View file

@ -121,6 +121,7 @@ export default {
"Elks": "46elks", "Elks": "46elks",
"GoogleChat": "Google Chat (Google Workspace)", "GoogleChat": "Google Chat (Google Workspace)",
"gorush": "Gorush", "gorush": "Gorush",
"GovNotify": "GOV Notify",
"gotify": "Gotify", "gotify": "Gotify",
"GrafanaOncall": "Grafana Oncall", "GrafanaOncall": "Grafana Oncall",
"HeiiOnCall": "Heii On-Call", "HeiiOnCall": "Heii On-Call",
@ -262,7 +263,7 @@ export default {
this.id = null; this.id = null;
this.notification = { this.notification = {
name: "", name: "",
type: "telegram", type: "GovNotify",
isDefault: false, isDefault: false,
}; };
} }

View file

@ -0,0 +1,75 @@
<template>
<div class="mb-3">
<label class="form-label">GOV Notify API Key</label>
<div class="input-group">
<input
v-if="!showApiKey"
type="text"
class="form-control"
value="************"
disabled
/>
<input
v-else
v-model="newApiKey"
type="text"
class="form-control"
placeholder="Enter new API key"
/>
<button class="btn btn-outline-secondary" type="button" @click="toggleApiKey">
{{ showApiKey ? "Cancel" : "Change" }}
</button>
</div>
</div>
<div class="mb-3">
<label class="form-label">Email Recipients (comma-separated)</label>
<input
v-model="$parent.notification.emailRecipients"
type="text"
class="form-control"
/>
</div>
<div class="mb-3">
<label class="form-label">SMS Recipients (comma-separated)</label>
<input
v-model="$parent.notification.smsRecipients"
type="text"
class="form-control"
/>
</div>
<div class="mb-3">
<label class="form-label">Email Template ID</label>
<input
v-model="$parent.notification.emailTemplateId"
type="text"
class="form-control"
/>
</div>
<div class="mb-3">
<label class="form-label">SMS Template ID</label>
<input
v-model="$parent.notification.smsTemplateId"
type="text"
class="form-control"
required
/>
</div>
</template>
<script>
export default {
data() {
return {
showApiKey: false,
newApiKey: "",
};
},
methods: {
toggleApiKey() {
if (this.showApiKey) {
this.newApiKey = "";
}
this.showApiKey = !this.showApiKey;
},
},
};
</script>

View file

@ -67,6 +67,7 @@ import Cellsynt from "./Cellsynt.vue";
import WPush from "./WPush.vue"; import WPush from "./WPush.vue";
import SIGNL4 from "./SIGNL4.vue"; import SIGNL4 from "./SIGNL4.vue";
import SendGrid from "./SendGrid.vue"; import SendGrid from "./SendGrid.vue";
import GovNotify from "./GovNotify.vue";
/** /**
* Manage all notification form. * Manage all notification form.
@ -142,6 +143,7 @@ const NotificationFormList = {
"Cellsynt": Cellsynt, "Cellsynt": Cellsynt,
"WPush": WPush, "WPush": WPush,
"SendGrid": SendGrid, "SendGrid": SendGrid,
"GovNotify": GovNotify
}; };
export default NotificationFormList; export default NotificationFormList;