diff --git a/package-lock.json b/package-lock.json index e34d44bbc..edbf89812 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,6 +61,7 @@ "node-radius-client": "~1.0.0", "nodemailer": "~6.9.13", "nostr-tools": "^2.10.4", + "notifications-node-client": "^8.2.1", "notp": "~2.0.3", "openid-client": "^5.4.2", "password-hash": "~1.2.2", @@ -12732,6 +12733,31 @@ "license": "MIT", "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": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/notp/-/notp-2.0.3.tgz", diff --git a/package.json b/package.json index 58160b8de..7f0785573 100644 --- a/package.json +++ b/package.json @@ -119,6 +119,7 @@ "node-radius-client": "~1.0.0", "nodemailer": "~6.9.13", "nostr-tools": "^2.10.4", + "notifications-node-client": "^8.2.1", "notp": "~2.0.3", "openid-client": "^5.4.2", "password-hash": "~1.2.2", diff --git a/server/auth.js b/server/auth.js index 597cf3d75..679e294d2 100644 --- a/server/auth.js +++ b/server/auth.js @@ -3,7 +3,10 @@ const passwordHash = require("./password-hash"); const { R } = require("redbean-node"); const { setting } = require("./util-server"); const { log } = require("../src/util"); -const { loginRateLimiter, apiRateLimiter } = require("./rate-limiter"); +const { + loginRateLimiter, + apiRateLimiter, +} = require("./rate-limiter"); const { Settings } = require("./settings"); const dayjs = require("dayjs"); diff --git a/server/client.js b/server/client.js index 72f0a4e8e..70741fe99 100644 --- a/server/client.js +++ b/server/client.js @@ -47,10 +47,11 @@ async function sendNotificationList(socket) { */ async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite = false) { let list = await R.getAll(` - SELECT * FROM heartbeat + SELECT * + FROM heartbeat WHERE monitor_id = ? ORDER BY time DESC - LIMIT 100 + LIMIT 100 `, [ monitorID, ]); diff --git a/server/database.js b/server/database.js index 0e6a7405d..da528f4a9 100644 --- a/server/database.js +++ b/server/database.js @@ -1,7 +1,13 @@ const fs = require("fs"); const { R } = require("redbean-node"); -const { setSetting, setting } = require("./util-server"); -const { log, sleep } = require("../src/util"); +const { + setSetting, + setting, +} = require("./util-server"); +const { + log, + sleep, +} = require("../src/util"); const knex = require("knex"); const path = require("path"); const { EmbeddedMariaDB } = require("./embedded-mariadb"); @@ -136,24 +142,24 @@ class Database { Database.dataDir = process.env.DATA_DIR || args["data-dir"] || "./data/"; Database.sqlitePath = path.join(Database.dataDir, "kuma.db"); - if (! fs.existsSync(Database.dataDir)) { + if (!fs.existsSync(Database.dataDir)) { fs.mkdirSync(Database.dataDir, { recursive: true }); } Database.uploadDir = path.join(Database.dataDir, "upload/"); - if (! fs.existsSync(Database.uploadDir)) { + if (!fs.existsSync(Database.uploadDir)) { fs.mkdirSync(Database.uploadDir, { recursive: true }); } // Create screenshot dir Database.screenshotDir = path.join(Database.dataDir, "screenshots/"); - if (! fs.existsSync(Database.screenshotDir)) { + if (!fs.existsSync(Database.screenshotDir)) { fs.mkdirSync(Database.screenshotDir, { recursive: true }); } Database.dockerTLSDir = path.join(Database.dataDir, "docker-tls/"); - if (! fs.existsSync(Database.dockerTLSDir)) { + if (!fs.existsSync(Database.dockerTLSDir)) { fs.mkdirSync(Database.dockerTLSDir, { recursive: true }); } @@ -231,7 +237,7 @@ class Database { if (dbConfig.type === "sqlite") { - if (! fs.existsSync(Database.sqlitePath)) { + if (!fs.existsSync(Database.sqlitePath)) { log.info("server", "Copying Database"); fs.copyFileSync(Database.templatePath, Database.sqlitePath); } @@ -252,7 +258,7 @@ class Database { idleTimeoutMillis: 120 * 1000, propagateCreateError: false, acquireTimeoutMillis: acquireConnectionTimeout, - } + }, }; } else if (dbConfig.type === "mariadb") { if (!/^\w+$/.test(dbConfig.dbName)) { @@ -451,7 +457,7 @@ class Database { static async patchSqlite() { let version = parseInt(await setting("database_version")); - if (! version) { + if (!version) { version = 0; } @@ -502,7 +508,7 @@ class Database { log.debug("db", "Database Patch 2.0 Process"); let databasePatchedFiles = await setting("databasePatchedFiles"); - if (! databasePatchedFiles) { + if (!databasePatchedFiles) { databasePatchedFiles = {}; } @@ -579,11 +585,11 @@ class Database { let id = await R.store(statusPage); 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", [ - id + id, ]); await R.exec("DELETE FROM setting WHERE type = 'statusPage'"); @@ -611,13 +617,13 @@ class Database { static async patch2Recursion(sqlFilename, databasePatchedFiles) { let value = this.patchList[sqlFilename]; - if (! value) { + if (!value) { log.info("db", sqlFilename + " skip"); return; } // Check if patched - if (! databasePatchedFiles[sqlFilename]) { + if (!databasePatchedFiles[sqlFilename]) { log.info("db", sqlFilename + " is not patched"); if (value.parents) { @@ -652,7 +658,7 @@ class Database { // Remove all comments (--) let lines = text.split("\n"); lines = lines.filter((line) => { - return ! line.startsWith("--"); + return !line.startsWith("--"); }); // Split statements by semicolon @@ -797,7 +803,8 @@ class Database { // Stop if stat_* tables are not empty 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; 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?)`); @@ -814,12 +821,12 @@ class Database { for (let monitor of monitors) { // Get a list of unique dates from the heartbeat table, using raw sql let dates = await R.getAll(` - SELECT DISTINCT DATE(time) AS date + SELECT DISTINCT DATE (time) AS date FROM heartbeat WHERE monitor_id = ? ORDER BY date ASC `, [ - monitor.monitor_id + monitor.monitor_id, ]); for (let date of dates) { @@ -833,7 +840,7 @@ class Database { SELECT status, ping, time FROM heartbeat WHERE monitor_id = ? - AND DATE(time) = ? + AND DATE (time) = ? ORDER BY time ASC `, [ monitor.monitor_id, date.date ]); @@ -887,19 +894,21 @@ class Database { log.info("db", "Deleting non-important heartbeats for monitor " + monitor.id); } await R.exec(` - DELETE FROM heartbeat + DELETE + FROM heartbeat WHERE monitor_id = ? - AND important = 0 - AND time < ${sqlHourOffset} - AND id NOT IN ( + AND important = 0 + AND time + < ${sqlHourOffset} + AND id NOT IN ( SELECT id FROM ( -- written this way for Maria's support - SELECT id - FROM heartbeat - WHERE monitor_id = ? - ORDER BY time DESC - LIMIT ? - ) AS limited_ids - ) + SELECT id + FROM heartbeat + WHERE monitor_id = ? + ORDER BY time DESC + LIMIT ? + ) AS limited_ids + ) `, [ monitor.id, -24, diff --git a/server/docker.js b/server/docker.js index ee6051dfa..249eff733 100644 --- a/server/docker.js +++ b/server/docker.js @@ -146,7 +146,7 @@ class DockerHost { static getHttpsAgentOptions(dockerType, url) { let baseOptions = { maxCachedSessions: 0, - rejectUnauthorized: true + rejectUnauthorized: true, }; let certOptions = {}; @@ -163,13 +163,13 @@ class DockerHost { certOptions = { ca, key, - cert + cert, }; } return { ...baseOptions, - ...certOptions + ...certOptions, }; } } diff --git a/server/jobs.js b/server/jobs.js index 0838731d0..5f60e3484 100644 --- a/server/jobs.js +++ b/server/jobs.js @@ -15,7 +15,7 @@ const jobs = [ interval: "*/5 * * * *", jobFunc: incrementalVacuum, croner: null, - } + }, ]; /** @@ -54,5 +54,5 @@ const stopBackgroundJobs = function () { module.exports = { initBackgroundJobs, - stopBackgroundJobs + stopBackgroundJobs, }; diff --git a/server/model/maintenance.js b/server/model/maintenance.js index 7111a18cb..f62b47a44 100644 --- a/server/model/maintenance.js +++ b/server/model/maintenance.js @@ -1,5 +1,9 @@ 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 dayjs = require("dayjs"); const Cron = require("croner"); @@ -192,7 +196,8 @@ class Maintenance extends BeanModel { * @returns {void} */ static validateCron(cron) { - let job = new Cron(cron, () => {}); + let job = new Cron(cron, () => { + }); job.stop(); } diff --git a/server/model/monitor.js b/server/model/monitor.js index 5999d93e7..b2d7078cf 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -1,11 +1,37 @@ const dayjs = require("dayjs"); const axios = require("axios"); const { Prometheus } = require("../prometheus"); -const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND, - SQL_DATETIME_FORMAT, evaluateJsonQuery +const { + log, + UP, + DOWN, + PENDING, + MAINTENANCE, + flipStatus, + MAX_INTERVAL_SECOND, + MIN_INTERVAL_SECOND, + SQL_DATETIME_FORMAT, + evaluateJsonQuery, } = require("../../src/util"); -const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius, grpcQuery, - redisPingAsync, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal +const { + tcping, + ping, + checkCertificate, + checkStatusCode, + getTotalClientInRoom, + setting, + mssqlQuery, + postgresQuery, + mysqlQuery, + setSetting, + httpNtlm, + radius, + grpcQuery, + redisPingAsync, + kafkaProducerAsync, + getOidcTokenClientCredentials, + rootCertificatesFingerprints, + axiosAbortSignal, } = require("../util-server"); const { R } = require("redbean-node"); 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:") { - const { certExpiryDaysRemaining, validCert } = await this.getCertExpiry(this.id); + const { + certExpiryDaysRemaining, + validCert, + } = await this.getCertExpiry(this.id); obj.certExpiryDaysRemaining = certExpiryDaysRemaining; obj.validCert = validCert; } @@ -218,13 +247,13 @@ class Monitor extends BeanModel { if (tlsInfo?.valid && tlsInfo?.certInfo?.daysRemaining) { return { certExpiryDaysRemaining: tlsInfo.certInfo.daysRemaining, - validCert: true + validCert: true, }; } } return { certExpiryDaysRemaining: "", - validCert: false + validCert: false, }; } @@ -334,7 +363,7 @@ class Monitor extends BeanModel { let beatInterval = this.interval; - if (! beatInterval) { + if (!beatInterval) { beatInterval = 1; } @@ -479,7 +508,7 @@ class Monitor extends BeanModel { ...(contentType ? { "Content-Type": contentType } : {}), ...(basicAuthHeader), ...(oauth2AuthHeader), - ...(this.headers ? JSON.parse(this.headers) : {}) + ...(this.headers ? JSON.parse(this.headers) : {}), }, maxRedirects: this.maxredirects, validateStatus: (status) => { @@ -504,7 +533,10 @@ class Monitor extends BeanModel { const proxy = await R.load("proxy", this.proxy_id); if (proxy && proxy.active) { - const { httpAgent, httpsAgent } = Proxy.createAgents(proxy, { + const { + httpAgent, + httpsAgent, + } = Proxy.createAgents(proxy, { httpsAgentOptions: httpsAgentOptions, }); @@ -518,7 +550,7 @@ class Monitor extends BeanModel { let jar = new CookieJar(); let httpsCookieAgentOptions = { ...httpsAgentOptions, - cookies: { jar } + cookies: { jar }, }; options.httpsAgent = new HttpsCookieAgent(httpsCookieAgentOptions); } @@ -600,7 +632,10 @@ class Monitor extends BeanModel { } else if (this.type === "json-query") { 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) { bean.status = UP; @@ -681,7 +716,7 @@ class Monitor extends BeanModel { params: { filter: filter, key: steamAPIKey, - } + }, }); if (res.data.response && res.data.response.servers && res.data.response.servers.length > 0) { @@ -690,7 +725,8 @@ class Monitor extends BeanModel { try { bean.ping = await ping(this.hostname, this.packetSize); - } catch (_) { } + } catch (_) { + } } else { throw new Error("Server not found on Steam"); } @@ -739,7 +775,7 @@ class Monitor extends BeanModel { } else if (dockerHost._dockerType === "tcp") { options.baseURL = DockerHost.patchDockerURL(dockerHost._dockerDaemon); 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; - if (! this.isStop) { + if (!this.isStop) { log.debug("monitor", `[${this.name}] SetTimeout for next check.`); let intervalRemainingMs = Math.max( 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`); @@ -1013,7 +1049,7 @@ class Monitor extends BeanModel { UptimeKumaServer.errorLog(e, false); 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"); this.heartbeatInterval = setTimeout(safeBeat, this.interval * 1000); } @@ -1047,7 +1083,7 @@ class Monitor extends BeanModel { username: this.basic_auth_user, password: this.basic_auth_pass, domain: this.authDomain, - workstation: this.authWorkstation ? this.authWorkstation : undefined + workstation: this.authWorkstation ? this.authWorkstation : undefined, }); } else { res = await axios.request(options); @@ -1065,8 +1101,9 @@ class Monitor extends BeanModel { let oauth2AuthHeader = { "Authorization": this.oauthAccessToken.token_type + " " + this.oauthAccessToken.access_token, }; - options.headers = { ...(options.headers), - ...(oauth2AuthHeader) + options.headers = { + ...(options.headers), + ...(oauth2AuthHeader), }; return this.makeAxiosRequest(options, true); @@ -1158,7 +1195,7 @@ class Monitor extends BeanModel { if (oldCertInfo.certInfo.fingerprint256 !== checkCertificateResult.certInfo.fingerprint256) { log.debug("monitor", "Resetting sent_history"); await R.exec("DELETE FROM notification_sent_history WHERE type = 'certificate' AND monitor_id = ?", [ - this.id + this.id, ]); } else { log.debug("monitor", "No need to reset sent_history"); @@ -1168,7 +1205,8 @@ class Monitor extends BeanModel { } else { log.debug("monitor", "Not valid object"); } - } catch (e) { } + } catch (e) { + } } @@ -1326,8 +1364,9 @@ class Monitor extends BeanModel { for (let notification of notificationList) { try { const heartbeatJSON = bean.toJSON(); - const monitorData = [{ id: monitor.id, - active: monitor.active + const monitorData = [{ + id: monitor.id, + active: monitor.active, }]; const preloadData = await Monitor.preparePreloadData(monitorData); // 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) { 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. log.debug("monitor", "No notification, no need to send cert notification"); return; @@ -1458,7 +1497,7 @@ class Monitor extends BeanModel { */ static async getPreviousHeartbeat(monitorID) { 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, value: row.value, name: row.name, - color: row.color + color: row.color, }); }); @@ -1687,7 +1726,7 @@ class Monitor extends BeanModel { */ static async unlinkAllChildren(groupID) { return await R.exec("UPDATE `monitor` SET parent = ? WHERE parent = ? ", [ - null, groupID + null, groupID, ]); } diff --git a/server/model/status_page.js b/server/model/status_page.js index 38f548ebb..02d80f6d1 100644 --- a/server/model/status_page.js +++ b/server/model/status_page.js @@ -8,7 +8,15 @@ const { marked } = require("marked"); const { Feed } = require("feed"); 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 { @@ -16,7 +24,7 @@ class StatusPage extends BeanModel { * Like this: { "test-uptime.kuma.pet": "default" } * @type {{}} */ - static domainMappingList = { }; + static domainMappingList = {}; /** * Handle responses to RSS pages @@ -26,7 +34,7 @@ class StatusPage extends BeanModel { */ static async handleStatusPageRSSResponse(response, slug) { let statusPage = await R.findOne("status_page", " slug = ? ", [ - slug + slug, ]); if (statusPage) { @@ -51,7 +59,7 @@ class StatusPage extends BeanModel { } let statusPage = await R.findOne("status_page", " slug = ? ", [ - slug + slug, ]); if (statusPage) { @@ -68,7 +76,10 @@ class StatusPage extends BeanModel { * @returns {Promise} the rendered html */ 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 host = `${proto}://${config.hostname || "localhost"}:${config.port}/status/${slug}`; @@ -135,7 +146,7 @@ class StatusPage extends BeanModel { // Preload data // Add jsesc, fix https://github.com/louislam/uptime-kuma/issues/2186 const escapedJSONObject = jsesc(await StatusPage.getStatusPageData(statusPage), { - "isScriptContext": true + "isScriptContext": true, }); const script = $(` @@ -174,7 +185,7 @@ class StatusPage extends BeanModel { } } - if (! hasUp) { + if (!hasUp) { status = STATUS_PAGE_ALL_DOWN; } @@ -223,7 +234,7 @@ class StatusPage extends BeanModel { const showTags = !!statusPage.show_tags; const list = await R.find("group", " public = 1 AND status_page_id = ? ORDER BY weight ", [ - statusPage.id + statusPage.id, ]); let heartbeats = []; @@ -236,7 +247,7 @@ class StatusPage extends BeanModel { heartbeats.push({ ...monitor, status: heartbeat.status, - time: heartbeat.time + time: heartbeat.time, }); } } @@ -251,7 +262,7 @@ class StatusPage extends BeanModel { return { heartbeats, - statusDescription + statusDescription, }; } @@ -279,7 +290,7 @@ class StatusPage extends BeanModel { const showTags = !!statusPage.show_tags; const list = await R.find("group", " public = 1 AND status_page_id = ? ORDER BY weight ", [ - statusPage.id + statusPage.id, ]); for (let groupBean of list) { @@ -442,7 +453,7 @@ class StatusPage extends BeanModel { */ static async slugToID(slug) { return await R.getCell("SELECT id FROM status_page WHERE slug = ? ", [ - slug + slug, ]); } diff --git a/server/model/user.js b/server/model/user.js index 329402ff5..d07cf8142 100644 --- a/server/model/user.js +++ b/server/model/user.js @@ -2,7 +2,10 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); const passwordHash = require("../password-hash"); const { R } = require("redbean-node"); const jwt = require("jsonwebtoken"); -const { shake256, SHAKE256_LENGTH } = require("../util-server"); +const { + shake256, + SHAKE256_LENGTH, +} = require("../util-server"); class User extends BeanModel { /** @@ -15,7 +18,7 @@ class User extends BeanModel { static async resetPassword(userID, newPassword) { await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [ passwordHash.generate(newPassword), - userID + userID, ]); } @@ -29,7 +32,7 @@ class User extends BeanModel { await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [ hashedPassword, - this.id + this.id, ]); this.password = hashedPassword; diff --git a/server/modules/apicache/apicache.js b/server/modules/apicache/apicache.js index 95a04d9e3..dd7f6d7ca 100644 --- a/server/modules/apicache/apicache.js +++ b/server/modules/apicache/apicache.js @@ -100,7 +100,7 @@ function ApiCache() { * Generated by Trelent */ 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; }); let debugEnv = process.env.DEBUG && process.env.DEBUG.split(",").indexOf("apicache") !== -1; @@ -210,7 +210,8 @@ function ApiCache() { try { redis.hset(key, "response", JSON.stringify(value)); redis.hset(key, "duration", duration); - redis.expire(key, duration / 1000, expireCallback || function () {}); + redis.expire(key, duration / 1000, expireCallback || function () { + }); } catch (err) { debug("[apicache] error in redis.hset()"); } @@ -247,8 +248,8 @@ function ApiCache() { } res._apicache.content = Buffer.concat( - [oldContent, content], - oldContent.length + content.length + [ oldContent, content ], + oldContent.length + content.length, ); } else { res._apicache.content = content; @@ -268,7 +269,7 @@ function ApiCache() { * @param {function(Object, Object):boolean} toggle */ function makeResponseCacheable(req, res, next, key, duration, strDuration, toggle) { - // monkeypatch res.end to create cache object + // monkeypatch res.end to create cache object res._apicache = { write: res.write, writeHead: res.writeHead, @@ -314,7 +315,7 @@ function ApiCache() { res.statusCode, headers, res._apicache.content, - encoding + encoding, ); cacheResponse(key, cacheObject, duration); @@ -367,7 +368,7 @@ function ApiCache() { let data = cacheObject.data; if (data && data.type === "Buffer") { data = - typeof data.data === "number" ? new Buffer.alloc(data.data) : new Buffer.from(data.data); + typeof data.data === "number" ? new Buffer.alloc(data.data) : new Buffer.from(data.data); } // test Etag against If-None-Match for 304 @@ -511,15 +512,15 @@ function ApiCache() { }; /** - * Return cache performance statistics (hit rate). Suitable for - * putting into a route: - * - * app.get('/api/cache/performance', (req, res) => { - * res.json(apicache.getPerformance()) - * }) - * - * @returns {any[]} - */ + * Return cache performance statistics (hit rate). Suitable for + * putting into a route: + * + * app.get('/api/cache/performance', (req, res) => { + * res.json(apicache.getPerformance()) + * }) + * + * @returns {any[]} + */ this.getPerformance = function () { return performanceArray.map(function (p) { return p.report(); @@ -528,7 +529,7 @@ function ApiCache() { /** * Get index of a group - * @param {string} group + * @param {string} group * @returns {number} */ this.getIndex = function (group) { @@ -543,9 +544,9 @@ function ApiCache() { * Express middleware * @param {(string|number)} strDuration Duration to cache responses * for. - * @param {function(Object, Object):boolean} middlewareToggle + * @param {function(Object, Object):boolean} middlewareToggle * @param {Object} localOptions Options for APICache - * @returns + * @returns */ this.middleware = function cache(strDuration, middlewareToggle, localOptions) { let duration = instance.getDuration(strDuration); @@ -573,7 +574,8 @@ function ApiCache() { * A Function for non tracking performance */ function NOOPCachePerformance() { - this.report = this.hit = this.miss = function () {}; // noop; + this.report = this.hit = this.miss = function () { + }; // noop; } /** @@ -762,8 +764,8 @@ function ApiCache() { } if ( req.headers["x-apicache-bypass"] || - req.headers["x-apicache-force-fetch"] || - (opt.respectCacheControl && req.headers["cache-control"] == "no-cache") + req.headers["x-apicache-force-fetch"] || + (opt.respectCacheControl && req.headers["cache-control"] == "no-cache") ) { return bypass(); } @@ -826,7 +828,7 @@ function ApiCache() { JSON.parse(obj.response), middlewareToggle, next, - duration + duration, ); } else { perf.miss(key); @@ -837,7 +839,7 @@ function ApiCache() { key, duration, strDuration, - middlewareToggle + middlewareToggle, ); } }); @@ -859,7 +861,7 @@ function ApiCache() { /** * Process options - * @param {Object} options + * @param {Object} options * @returns {Object} */ this.options = function (options) { diff --git a/server/modules/apicache/index.js b/server/modules/apicache/index.js index b8bb9b354..1938f5dd9 100644 --- a/server/modules/apicache/index.js +++ b/server/modules/apicache/index.js @@ -2,7 +2,7 @@ const apicache = require("./apicache"); apicache.options({ headerBlacklist: [ - "cache-control" + "cache-control", ], headers: { // Disable client side cache, only server side cache. diff --git a/server/modules/apicache/memory-cache.js b/server/modules/apicache/memory-cache.js index a91eee324..1e75bae32 100644 --- a/server/modules/apicache/memory-cache.js +++ b/server/modules/apicache/memory-cache.js @@ -4,7 +4,7 @@ function MemoryCache() { } /** - * + * * @param {string} key Key to store cache as * @param {any} value Value to store * @param {number} time Time to store for @@ -22,7 +22,7 @@ MemoryCache.prototype.add = function (key, value, time, timeoutCallback) { timeout: setTimeout(function () { instance.delete(key); return timeoutCallback && typeof timeoutCallback === "function" && timeoutCallback(value, key); - }, time) + }, time), }; this.cache[key] = entry; @@ -52,7 +52,7 @@ MemoryCache.prototype.delete = function (key) { /** * Get value of key - * @param {string} key + * @param {string} key * @returns {Object} */ MemoryCache.prototype.get = function (key) { @@ -63,7 +63,7 @@ MemoryCache.prototype.get = function (key) { /** * Get value of cache entry - * @param {string} key + * @param {string} key * @returns {any} */ MemoryCache.prototype.getValue = function (key) { diff --git a/server/modules/axios-ntlm/lib/flags.js b/server/modules/axios-ntlm/lib/flags.js index c16028cad..86c06033b 100644 --- a/server/modules/axios-ntlm/lib/flags.js +++ b/server/modules/axios-ntlm/lib/flags.js @@ -1,4 +1,4 @@ -'use strict'; +"use strict"; // Original file https://raw.githubusercontent.com/elasticio/node-ntlm-client/master/lib/flags.js module.exports.NTLMFLAG_NEGOTIATE_UNICODE = 1 << 0; /* Indicates that Unicode strings are supported for use in security buffer @@ -74,4 +74,4 @@ module.exports.NTLMFLAG_NEGOTIATE_KEY_EXCHANGE = 1 << 30; /* Indicates that the client will provide an encrypted master key in the "Session Key" field of the Type 3 message. */ module.exports.NTLMFLAG_NEGOTIATE_56 = 1 << 31; -//# sourceMappingURL=flags.js.map \ No newline at end of file +//# sourceMappingURL=flags.js.map diff --git a/server/modules/axios-ntlm/lib/hash.js b/server/modules/axios-ntlm/lib/hash.js index 4e5aa26b4..73cf97e4f 100644 --- a/server/modules/axios-ntlm/lib/hash.js +++ b/server/modules/axios-ntlm/lib/hash.js @@ -1,6 +1,7 @@ -'use strict'; +"use strict"; // 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) { var buf = new Buffer.alloc(24), pwBuffer = new Buffer.alloc(21).fill(0); lmhash.copy(pwBuffer); @@ -9,19 +10,21 @@ function createLMResponse(challenge, lmhash) { calculateDES(pwBuffer.slice(14), challenge).copy(buf, 16); return buf; } + 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) { buf.fill(0); return buf; } pwBuffer.fill(0); - pwBuffer.write(password.toUpperCase(), 0, 'ascii'); + pwBuffer.write(password.toUpperCase(), 0, "ascii"); return Buffer.concat([ calculateDES(pwBuffer.slice(0, 7), magicKey), - calculateDES(pwBuffer.slice(7), magicKey) + calculateDES(pwBuffer.slice(7), magicKey), ]); } + function calculateDES(key, message) { var desKey = new Buffer.alloc(8); desKey[0] = key[0] & 0xFE; @@ -39,9 +42,10 @@ function calculateDES(key, message) { } 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); } + function createNTLMResponse(challenge, ntlmhash) { var buf = new Buffer.alloc(24), ntlmBuffer = new Buffer.alloc(21).fill(0); ntlmhash.copy(ntlmBuffer); @@ -50,30 +54,36 @@ function createNTLMResponse(challenge, ntlmhash) { calculateDES(ntlmBuffer.slice(14), challenge).copy(buf, 16); return buf; } + function createNTLMHash(password) { - var md4sum = crypto.createHash('md4'); - md4sum.update(new Buffer.from(password, 'ucs2')); + var md4sum = crypto.createHash("md4"); + md4sum.update(new Buffer.from(password, "ucs2")); return md4sum.digest(); } + function createNTLMv2Hash(ntlmhash, username, authTargetName) { - var hmac = crypto.createHmac('md5', ntlmhash); - hmac.update(new Buffer.from(username.toUpperCase() + authTargetName, 'ucs2')); + var hmac = crypto.createHmac("md5", ntlmhash); + hmac.update(new Buffer.from(username.toUpperCase() + authTargetName, "ucs2")); return hmac.digest(); } + 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 type2message.challenge.copy(buf, 8); //client nonce - buf.write(nonce || createPseudoRandomValue(16), 16, 'hex'); + buf.write(nonce || createPseudoRandomValue(16), 16, "hex"); //create hash hmac.update(buf.slice(8)); var hashedBuffer = hmac.digest(); hashedBuffer.copy(buf); return buf; } + 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 //server challenge type2message.challenge.copy(buf, 8); @@ -86,12 +96,12 @@ function createNTLMv2Response(type2message, username, ntlmhash, nonce, targetNam // maybe think about a different solution here // 11644473600000 = diff between 1970 and 1601 var timestamp = ((Date.now() + 11644473600000) * 10000).toString(16); - 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 timestampLow = Number("0x" + timestamp.substring(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(timestampHigh, 28, false); //random client nonce - buf.write(nonce || createPseudoRandomValue(16), 32, 'hex'); + buf.write(nonce || createPseudoRandomValue(16), 32, "hex"); //zero buf.writeUInt32LE(0, 40); //complete target information block from type 2 message @@ -103,13 +113,15 @@ function createNTLMv2Response(type2message, username, ntlmhash, nonce, targetNam hashedBuffer.copy(buf); return buf; } + function createPseudoRandomValue(length) { - var str = ''; + var str = ""; while (str.length < length) { str += Math.floor(Math.random() * 16).toString(16); } return str; } + module.exports = { createLMHash: createLMHash, createNTLMHash: createNTLMHash, @@ -117,6 +129,6 @@ module.exports = { createNTLMResponse: createNTLMResponse, createLMv2Response: createLMv2Response, createNTLMv2Response: createNTLMv2Response, - createPseudoRandomValue: createPseudoRandomValue + createPseudoRandomValue: createPseudoRandomValue, }; -//# sourceMappingURL=hash.js.map \ No newline at end of file +//# sourceMappingURL=hash.js.map diff --git a/server/modules/axios-ntlm/lib/ntlm.js b/server/modules/axios-ntlm/lib/ntlm.js index 54490c0a6..eda4b1b76 100644 --- a/server/modules/axios-ntlm/lib/ntlm.js +++ b/server/modules/axios-ntlm/lib/ntlm.js @@ -1,13 +1,14 @@ -'use strict'; +"use strict"; // 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"; + function createType1Message(workstation, target) { var dataPos = 32, pos = 0, buf = new Buffer.alloc(1024); workstation = workstation === undefined ? os.hostname() : workstation; - target = target === undefined ? '' : target; + target = target === undefined ? "" : target; //signature - buf.write(NTLMSIGNATURE, pos, NTLMSIGNATURE.length, 'ascii'); + buf.write(NTLMSIGNATURE, pos, NTLMSIGNATURE.length, "ascii"); pos += NTLMSIGNATURE.length; //message type buf.writeUInt32LE(1, pos); @@ -27,7 +28,7 @@ function createType1Message(workstation, target) { buf.writeUInt32LE(target.length === 0 ? 0 : dataPos, pos); pos += 4; if (target.length > 0) { - dataPos += buf.write(target, dataPos, 'ascii'); + dataPos += buf.write(target, dataPos, "ascii"); } //workstation security buffer buf.writeUInt16LE(workstation.length, pos); @@ -37,39 +38,39 @@ function createType1Message(workstation, target) { buf.writeUInt32LE(workstation.length === 0 ? 0 : dataPos, pos); pos += 4; 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) { if (str === undefined) { - throw new Error('Invalid argument'); + throw new Error("Invalid argument"); } //convenience - if (Object.prototype.toString.call(str) !== '[object String]') { - if (str.hasOwnProperty('headers') && str.headers.hasOwnProperty('www-authenticate')) { - str = str.headers['www-authenticate']; - } - else { - throw new Error('Invalid argument'); + if (Object.prototype.toString.call(str) !== "[object String]") { + if (str.hasOwnProperty("headers") && str.headers.hasOwnProperty("www-authenticate")) { + str = str.headers["www-authenticate"]; + } else { + throw new Error("Invalid argument"); } } var ntlmMatch = /^NTLM ([^,\s]+)/.exec(str); if (ntlmMatch) { str = ntlmMatch[1]; } - var buf = new Buffer.from(str, 'base64'), obj = {}; + var buf = new Buffer.from(str, "base64"), obj = {}; //check signature - if (buf.toString('ascii', 0, NTLMSIGNATURE.length) !== NTLMSIGNATURE) { - throw new Error('Invalid message signature: ' + str); + if (buf.toString("ascii", 0, NTLMSIGNATURE.length) !== NTLMSIGNATURE) { + throw new Error("Invalid message signature: " + str); } //check message type 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 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.challenge = buf.slice(24, 32); //read target name @@ -78,10 +79,10 @@ function decodeType2Message(str) { //skipping allocated space var offset = buf.readUInt32LE(16); if (length === 0) { - return ''; + return ""; } 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); })(); @@ -98,7 +99,7 @@ function decodeType2Message(str) { return info; } if ((offset + length) > buf.length || offset < 32) { - throw new Error('Bad type 2 message'); + throw new Error("Bad type 2 message"); } var pos = offset; while (pos < (offset + length)) { @@ -113,37 +114,38 @@ function decodeType2Message(str) { var blockTypeStr = void 0; switch (blockType) { case 1: - blockTypeStr = 'SERVER'; + blockTypeStr = "SERVER"; break; case 2: - blockTypeStr = 'DOMAIN'; + blockTypeStr = "DOMAIN"; break; case 3: - blockTypeStr = 'FQDN'; + blockTypeStr = "FQDN"; break; case 4: - blockTypeStr = 'DNS'; + blockTypeStr = "DNS"; break; case 5: - blockTypeStr = 'PARENT_DNS'; + blockTypeStr = "PARENT_DNS"; break; default: - blockTypeStr = ''; + blockTypeStr = ""; break; } if (blockTypeStr) { - info[blockTypeStr] = buf.toString('ucs2', pos, pos + blockLength); + info[blockTypeStr] = buf.toString("ucs2", pos, pos + blockLength); } pos += blockLength; } return { parsed: info, - buffer: targetInfoBuffer + buffer: targetInfoBuffer, }; })(); } return obj; } + function createType3Message(type2Message, username, password, workstation, target) { var dataPos = 52, buf = new Buffer.alloc(1024); if (workstation === undefined) { @@ -153,12 +155,14 @@ function createType3Message(type2Message, username, password, workstation, targe target = type2Message.targetName; } //signature - buf.write(NTLMSIGNATURE, 0, NTLMSIGNATURE.length, 'ascii'); + buf.write(NTLMSIGNATURE, 0, NTLMSIGNATURE.length, "ascii"); //message type buf.writeUInt32LE(3, 8); if (type2Message.version === 2) { 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 buf.writeUInt16LE(lmv2.length, 12); buf.writeUInt16LE(lmv2.length, 14); @@ -171,9 +175,10 @@ function createType3Message(type2Message, username, password, workstation, targe buf.writeUInt32LE(dataPos, 24); ntlmv2.copy(buf, dataPos); dataPos += ntlmv2.length; - } - else { - var lmHash = hash.createLMHash(password), ntlmHash = hash.createNTLMHash(password), lm = hash.createLMResponse(type2Message.challenge, lmHash), ntlm = hash.createNTLMResponse(type2Message.challenge, ntlmHash); + } else { + var lmHash = hash.createLMHash(password), ntlmHash = hash.createNTLMHash(password), + lm = hash.createLMResponse(type2Message.challenge, lmHash), + ntlm = hash.createNTLMResponse(type2Message.challenge, ntlmHash); //lm security buffer buf.writeUInt16LE(lm.length, 12); buf.writeUInt16LE(lm.length, 14); @@ -188,18 +193,18 @@ function createType3Message(type2Message, username, password, workstation, targe dataPos += ntlm.length; } //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, 30); + buf.writeUInt16LE(type2Message.encoding === "ascii" ? target.length : target.length * 2, 28); + buf.writeUInt16LE(type2Message.encoding === "ascii" ? target.length : target.length * 2, 30); buf.writeUInt32LE(dataPos, 32); dataPos += buf.write(target, dataPos, type2Message.encoding); //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, 38); + buf.writeUInt16LE(type2Message.encoding === "ascii" ? username.length : username.length * 2, 36); + buf.writeUInt16LE(type2Message.encoding === "ascii" ? username.length : username.length * 2, 38); buf.writeUInt32LE(dataPos, 40); dataPos += buf.write(username, dataPos, type2Message.encoding); //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, 46); + buf.writeUInt16LE(type2Message.encoding === "ascii" ? workstation.length : workstation.length * 2, 44); + buf.writeUInt16LE(type2Message.encoding === "ascii" ? workstation.length : workstation.length * 2, 46); buf.writeUInt32LE(dataPos, 48); dataPos += buf.write(workstation, dataPos, type2Message.encoding); if (type2Message.version === 2) { @@ -210,11 +215,12 @@ function createType3Message(type2Message, username, password, workstation, targe //flags buf.writeUInt32LE(type2Message.flags, 60); } - return 'NTLM ' + buf.toString('base64', 0, dataPos); + return "NTLM " + buf.toString("base64", 0, dataPos); } + module.exports = { createType1Message: createType1Message, decodeType2Message: decodeType2Message, - createType3Message: createType3Message + createType3Message: createType3Message, }; -//# sourceMappingURL=ntlm.js.map \ No newline at end of file +//# sourceMappingURL=ntlm.js.map diff --git a/server/modules/axios-ntlm/lib/ntlmClient.js b/server/modules/axios-ntlm/lib/ntlmClient.js index 682de5f9a..dbd6ba189 100644 --- a/server/modules/axios-ntlm/lib/ntlmClient.js +++ b/server/modules/axios-ntlm/lib/ntlmClient.js @@ -1,57 +1,172 @@ "use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (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; +var __createBinding = (this && this.__createBinding) || (Object.create ? (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]; })); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function (o, v) { + Object.defineProperty(o, "default", { + enumerable: true, + value: v, + }); +}) : function (o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; + if (mod && mod.__esModule) { + return mod; + } 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); return result; }; 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) { - function fulfilled(value) { try { 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); } + function fulfilled(value) { + try { + 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()); }); }; 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; - 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) { - if (f) throw new TypeError("Generator is already executing."); - 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]) { - case 0: case 1: t = op; 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: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.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; + var _ = { + label: 0, + sent: function () { + if (t[0] & 1) { + throw t[1]; } - op = body.call(thisArg, _); - } catch (e) { 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 }; + 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) { + if (f) { + throw new TypeError("Generator is already executing."); + } + 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]) { + case 0: + case 1: + t = op; + 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: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { + _ = 0; + continue; + } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { + _.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, _); + } catch (e) { + 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) { @@ -64,12 +179,13 @@ var ntlm = __importStar(require("./ntlm")); var https = __importStar(require("https")); var http = __importStar(require("http")); var dev_null_1 = __importDefault(require("dev-null")); + /** -* @param credentials An NtlmCredentials object containing the username and password -* @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 -*/ + * @param credentials An NtlmCredentials object containing the username and password + * @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 + */ function NtlmClient(credentials, AxiosConfig) { var _this = this; var config = AxiosConfig !== null && AxiosConfig !== void 0 ? AxiosConfig : {}; @@ -82,46 +198,56 @@ function NtlmClient(credentials, AxiosConfig) { var client = axios_1.default.create(config); client.interceptors.response.use(function (response) { return response; - }, function (err) { return __awaiter(_this, void 0, void 0, function () { - var error, t1Msg, t2Msg, t3Msg, stream_1; - var _a; - return __generator(this, function (_b) { - switch (_b.label) { - case 0: - error = err.response; - if (!(error && error.status === 401 - && error.headers['www-authenticate'] - && error.headers['www-authenticate'].includes('NTLM'))) return [3 /*break*/, 3]; - // This length check is a hack because SharePoint is awkward and will - // include the Negotiate option when responding with the T2 message - // There is nore we could do to ensure we are processing correctly, - // but this is the easiest option for now - if (error.headers['www-authenticate'].length < 50) { - t1Msg = ntlm.createType1Message(credentials.workstation, credentials.domain); - error.config.headers["Authorization"] = t1Msg; - } - else { - t2Msg = ntlm.decodeType2Message((error.headers['www-authenticate'].match(/^NTLM\s+(.+?)(,|\s+|$)/) || [])[1]); - t3Msg = ntlm.createType3Message(t2Msg, credentials.username, credentials.password, credentials.workstation, credentials.domain); - error.config.headers["X-retry"] = "false"; - error.config.headers["Authorization"] = t3Msg; - } - if (!(error.config.responseType === "stream")) return [3 /*break*/, 2]; - stream_1 = (_a = err.response) === null || _a === void 0 ? void 0 : _a.data; - if (!(stream_1 && !stream_1.readableEnded)) return [3 /*break*/, 2]; - return [4 /*yield*/, new Promise(function (resolve) { + }, function (err) { + return __awaiter(_this, void 0, void 0, function () { + var error, t1Msg, t2Msg, t3Msg, stream_1; + var _a; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + error = err.response; + if (!(error && error.status === 401 + && error.headers["www-authenticate"] + && error.headers["www-authenticate"].includes("NTLM"))) { + return [ 3 /*break*/, 3 ]; + } + // This length check is a hack because SharePoint is awkward and will + // include the Negotiate option when responding with the T2 message + // There is nore we could do to ensure we are processing correctly, + // but this is the easiest option for now + if (error.headers["www-authenticate"].length < 50) { + t1Msg = ntlm.createType1Message(credentials.workstation, credentials.domain); + error.config.headers["Authorization"] = t1Msg; + } else { + t2Msg = ntlm.decodeType2Message((error.headers["www-authenticate"].match(/^NTLM\s+(.+?)(,|\s+|$)/) || [])[1]); + t3Msg = ntlm.createType3Message(t2Msg, credentials.username, credentials.password, credentials.workstation, credentials.domain); + error.config.headers["X-retry"] = "false"; + error.config.headers["Authorization"] = t3Msg; + } + if (!(error.config.responseType === "stream")) { + return [ 3 /*break*/, 2 ]; + } + stream_1 = (_a = err.response) === null || _a === void 0 ? void 0 : _a.data; + if (!(stream_1 && !stream_1.readableEnded)) { + return [ 3 /*break*/, 2 ]; + } + return [ 4 /*yield*/, new Promise(function (resolve) { stream_1.pipe((0, dev_null_1.default)()); stream_1.once('close', resolve); - })]; - case 1: - _b.sent(); - _b.label = 2; - case 2: return [2 /*return*/, client(error.config)]; - case 3: throw err; - } + }) ]; + case 1: + _b.sent(); + _b.label = 2; + case 2: + return [ 2 /*return*/, client(error.config) ]; + case 3: + throw err; + } + }); }); - }); }); + }); return client; } + exports.NtlmClient = NtlmClient; -//# sourceMappingURL=ntlmClient.js.map \ No newline at end of file +//# sourceMappingURL=ntlmClient.js.map diff --git a/server/modules/dayjs/plugin/timezone.d.ts b/server/modules/dayjs/plugin/timezone.d.ts index d504f6927..4a66bb355 100644 --- a/server/modules/dayjs/plugin/timezone.d.ts +++ b/server/modules/dayjs/plugin/timezone.d.ts @@ -1,20 +1,24 @@ -import { PluginFunc, ConfigType } from 'dayjs' +import { PluginFunc, ConfigType } from "dayjs"; -declare const plugin: PluginFunc +declare const plugin: PluginFunc; export = plugin -declare module 'dayjs' { - interface Dayjs { - tz(timezone?: string, keepLocalTime?: boolean): Dayjs - offsetName(type?: 'short' | 'long'): string | undefined - } +declare module "dayjs" { + interface Dayjs { + tz(timezone?: string, keepLocalTime?: boolean): Dayjs; - interface DayjsTimezone { - (date: ConfigType, timezone?: string): Dayjs - (date: ConfigType, format: string, timezone?: string): Dayjs - guess(): string - setDefault(timezone?: string): void - } + offsetName(type?: "short" | "long"): string | undefined; + } - const tz: DayjsTimezone + interface DayjsTimezone { + (date: ConfigType, timezone?: string): Dayjs; + + (date: ConfigType, format: string, timezone?: string): Dayjs; + + guess(): string; + + setDefault(timezone?: string): void; + } + + const tz: DayjsTimezone; } diff --git a/server/modules/dayjs/plugin/timezone.js b/server/modules/dayjs/plugin/timezone.js index de709ae4b..32abb4270 100644 --- a/server/modules/dayjs/plugin/timezone.js +++ b/server/modules/dayjs/plugin/timezone.js @@ -15,7 +15,7 @@ day: 2, hour: 3, minute: 4, - second: 5 + second: 5, }; let e = {}; return function (n, i, o) { @@ -37,7 +37,7 @@ hour: "2-digit", minute: "2-digit", second: "2-digit", - timeZoneName: i + timeZoneName: i, }), e[o] = r), r; }(n, i); return r.formatToParts(o); diff --git a/server/monitor-types/dns.js b/server/monitor-types/dns.js index 8b87932fe..5e57ba476 100644 --- a/server/monitor-types/dns.js +++ b/server/monitor-types/dns.js @@ -1,5 +1,8 @@ const { MonitorType } = require("./monitor-type"); -const { UP, DOWN } = require("../../src/util"); +const { + UP, + DOWN, +} = require("../../src/util"); const dayjs = require("dayjs"); const { dnsResolve } = require("../util-server"); const { R } = require("redbean-node"); @@ -14,7 +17,7 @@ class DnsMonitorType extends MonitorType { supportsConditions = true; conditionVariables = [ - new ConditionVariable("record", defaultStringOperators ), + new ConditionVariable("record", defaultStringOperators), ]; /** diff --git a/server/monitor-types/mqtt.js b/server/monitor-types/mqtt.js index ad734ce8e..923c4d418 100644 --- a/server/monitor-types/mqtt.js +++ b/server/monitor-types/mqtt.js @@ -1,5 +1,8 @@ const { MonitorType } = require("./monitor-type"); -const { log, UP } = require("../../src/util"); +const { + log, + UP, +} = require("../../src/util"); const mqtt = require("mqtt"); const jsonata = require("jsonata"); @@ -57,7 +60,12 @@ class MqttMonitorType extends MonitorType { */ mqttAsync(hostname, topic, options = {}) { 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 if (!/^(?:http|mqtt|ws)s?:\/\//.test(hostname)) { @@ -77,7 +85,7 @@ class MqttMonitorType extends MonitorType { let client = mqtt.connect(mqttUrl, { username, password, - clientId: "uptime-kuma_" + Math.random().toString(16).substr(2, 8) + clientId: "uptime-kuma_" + Math.random().toString(16).substr(2, 8), }); client.on("connect", () => { diff --git a/server/monitor-types/rabbitmq.js b/server/monitor-types/rabbitmq.js index 165a0ed91..d1868fd01 100644 --- a/server/monitor-types/rabbitmq.js +++ b/server/monitor-types/rabbitmq.js @@ -1,5 +1,9 @@ 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 axios = require("axios"); @@ -21,7 +25,7 @@ class RabbitMqMonitorType extends MonitorType { for (let baseUrl of baseUrls) { try { // Without a trailing slash, path in baseUrl will be removed. https://example.com/api -> https://example.com - if ( !baseUrl.endsWith("/") ) { + if (!baseUrl.endsWith("/")) { baseUrl += "/"; } const options = { diff --git a/server/monitor-types/real-browser-monitor-type.js b/server/monitor-types/real-browser-monitor-type.js index 2a2871d2c..c1538ca1c 100644 --- a/server/monitor-types/real-browser-monitor-type.js +++ b/server/monitor-types/real-browser-monitor-type.js @@ -1,6 +1,9 @@ const { MonitorType } = require("./monitor-type"); const { chromium } = require("playwright-core"); -const { UP, log } = require("../../src/util"); +const { + UP, + log, +} = require("../../src/util"); const { Settings } = require("../settings"); const commandExistsSync = require("command-exists").sync; const childProcess = require("child_process"); @@ -122,7 +125,7 @@ async function prepareChromeExecutable(executablePath) { executablePath = "/usr/bin/chromium"; // Install chromium in container via apt install - if ( !commandExistsSync(executablePath)) { + if (!commandExistsSync(executablePath)) { await new Promise((resolve, reject) => { 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"); @@ -213,6 +216,7 @@ async function testChrome(executablePath) { throw new Error(e.message); } } + // test remote browser /** * @param {string} remoteBrowserURL Remote Browser URL @@ -228,6 +232,7 @@ async function testRemoteBrowser(remoteBrowserURL) { throw new Error(e.message); } } + class RealBrowserMonitorType extends MonitorType { name = "real-browser"; diff --git a/server/monitor-types/snmp.js b/server/monitor-types/snmp.js index a1760fa3d..a9066514e 100644 --- a/server/monitor-types/snmp.js +++ b/server/monitor-types/snmp.js @@ -1,5 +1,9 @@ const { MonitorType } = require("./monitor-type"); -const { UP, log, evaluateJsonQuery } = require("../../src/util"); +const { + UP, + log, + evaluateJsonQuery, +} = require("../../src/util"); const snmp = require("net-snmp"); 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. 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) { heartbeat.status = UP; diff --git a/server/notification-providers/gov-notify.js b/server/notification-providers/gov-notify.js new file mode 100644 index 000000000..5c95b4900 --- /dev/null +++ b/server/notification-providers/gov-notify.js @@ -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} 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; diff --git a/server/notification-providers/home-assistant.js b/server/notification-providers/home-assistant.js index 4536b2a43..6de759793 100644 --- a/server/notification-providers/home-assistant.js +++ b/server/notification-providers/home-assistant.js @@ -20,19 +20,21 @@ class HomeAssistant extends NotificationProvider { { title: "Uptime Kuma", message: msg, - ...(notificationService !== "persistent_notification" && { data: { - name: monitorJSON?.name, - status: heartbeatJSON?.status, - channel: "Uptime Kuma", - icon_url: "https://github.com/louislam/uptime-kuma/blob/master/public/icon.png?raw=true", - } }), + ...(notificationService !== "persistent_notification" && { + data: { + name: monitorJSON?.name, + status: heartbeatJSON?.status, + channel: "Uptime Kuma", + icon_url: "https://github.com/louislam/uptime-kuma/blob/master/public/icon.png?raw=true", + }, + }), }, { headers: { Authorization: `Bearer ${notification.longLivedAccessToken}`, "Content-Type": "application/json", }, - } + }, ); return okMsg; diff --git a/server/notification.js b/server/notification.js index e7977eb4a..cac3ebdeb 100644 --- a/server/notification.js +++ b/server/notification.js @@ -69,6 +69,7 @@ const Cellsynt = require("./notification-providers/cellsynt"); const Onesender = require("./notification-providers/onesender"); const Wpush = require("./notification-providers/wpush"); const SendGrid = require("./notification-providers/send-grid"); +const GovNotify = require("./notification-providers/gov-notify"); class Notification { @@ -154,10 +155,11 @@ class Notification { new GtxMessaging(), new Cellsynt(), new Wpush(), - new SendGrid() + new SendGrid(), + new GovNotify(), ]; for (let item of list) { - if (! item.name) { + if (!item.name) { throw new Error("Notification provider without name"); } @@ -181,7 +183,7 @@ class Notification { if (this.providerList[notification.type]) { return this.providerList[notification.type].send(notification, msg, monitorJSON, heartbeatJSON); } 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, ]); - if (! bean) { + if (!bean) { throw new Error("notification not found"); } @@ -234,7 +236,7 @@ class Notification { userID, ]); - if (! bean) { + if (!bean) { throw new Error("notification not found"); } @@ -261,7 +263,7 @@ class Notification { */ async function applyNotificationEveryMonitor(notificationID, userID) { let monitors = await R.getAll("SELECT id FROM monitor WHERE user_id = ?", [ - userID + userID, ]); for (let i = 0; i < monitors.length; i++) { @@ -270,7 +272,7 @@ async function applyNotificationEveryMonitor(notificationID, userID) { notificationID, ]); - if (! checkNotification) { + if (!checkNotification) { let relation = R.dispense("monitor_notification"); relation.monitor_id = monitors[i].id; relation.notification_id = notificationID; diff --git a/server/prometheus.js b/server/prometheus.js index f26125d2c..49d98ae52 100644 --- a/server/prometheus.js +++ b/server/prometheus.js @@ -12,24 +12,24 @@ const commonLabels = [ const monitorCertDaysRemaining = new PrometheusClient.Gauge({ name: "monitor_cert_days_remaining", help: "The number of days remaining until the certificate expires", - labelNames: commonLabels + labelNames: commonLabels, }); const monitorCertIsValid = new PrometheusClient.Gauge({ name: "monitor_cert_is_valid", help: "Is the certificate still valid? (1 = Yes, 0= No)", - labelNames: commonLabels + labelNames: commonLabels, }); const monitorResponseTime = new PrometheusClient.Gauge({ name: "monitor_response_time", help: "Monitor Response Time (ms)", - labelNames: commonLabels + labelNames: commonLabels, }); const monitorStatus = new PrometheusClient.Gauge({ name: "monitor_status", help: "Monitor Status (1 = UP, 0= DOWN, 2= PENDING, 3= MAINTENANCE)", - labelNames: commonLabels + labelNames: commonLabels, }); class Prometheus { @@ -44,7 +44,7 @@ class Prometheus { monitor_type: monitor.type, monitor_url: monitor.url, monitor_hostname: monitor.hostname, - monitor_port: monitor.port + monitor_port: monitor.port, }; } @@ -119,5 +119,5 @@ class Prometheus { } module.exports = { - Prometheus + Prometheus, }; diff --git a/server/proxy.js b/server/proxy.js index d38e3e4f1..5501b3a9a 100644 --- a/server/proxy.js +++ b/server/proxy.js @@ -36,7 +36,7 @@ class Proxy { if (!this.SUPPORTED_PROXY_PROTOCOLS.includes(proxy.protocol)) { throw new Error(` 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 */ static createAgents(proxy, options) { - const { httpAgentOptions, httpsAgentOptions } = options || {}; + const { + httpAgentOptions, + httpsAgentOptions, + } = options || {}; let agent; let httpAgent; let httpsAgent; @@ -150,12 +153,13 @@ class Proxy { httpsAgent = agent; break; - default: throw new Error(`Unsupported proxy protocol provided. ${proxy.protocol}`); + default: + throw new Error(`Unsupported proxy protocol provided. ${proxy.protocol}`); } return { httpAgent, - httpsAgent + httpsAgent, }; } diff --git a/server/routers/api-router.js b/server/routers/api-router.js index ed6db2cd1..34f9b15b0 100644 --- a/server/routers/api-router.js +++ b/server/routers/api-router.js @@ -11,7 +11,15 @@ const { R } = require("redbean-node"); const apicache = require("../modules/apicache"); const Monitor = require("../model/monitor"); 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 { UptimeKumaServer } = require("../uptime-kuma-server"); const { makeBadge } = require("badge-maker"); @@ -28,7 +36,7 @@ let io = server.io; router.get("/api/entry-page", async (request, response) => { allowDevAllOrigin(response); - let result = { }; + let result = {}; let hostname = request.hostname; if ((await setting("trustProxy")) && 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 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."); } @@ -127,7 +135,7 @@ router.all("/api/push/:pushToken", async (request, response) => { } catch (e) { response.status(404).json({ 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 public = 1 `, - [ requestedMonitorId ] + [ requestedMonitorId ], ); 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 public = 1 `, - [ requestedMonitorId ] + [ requestedMonitorId ], ); const badgeValues = { style }; @@ -362,7 +370,7 @@ router.get("/api/badge/:id/avg-response/:duration?", cache("5 minutes"), async ( request.params.duration ? parseInt(request.params.duration, 10) : 24, - 720 + 720, ); 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 heartbeat.monitor_id = ? `, - [ -requestedDuration, requestedMonitorId ] + [ -requestedDuration, requestedMonitorId ], )); 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 public = 1 `, - [ requestedMonitorId ] + [ requestedMonitorId ], ); 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 public = 1 `, - [ requestedMonitorId ] + [ requestedMonitorId ], ); const badgeValues = { style }; @@ -540,7 +548,7 @@ router.get("/api/badge/:id/response", cache("5 minutes"), async (request, respon badgeValues.color = badgeConstants.naColor; } else { const heartbeat = await Monitor.getPreviousHeartbeat( - requestedMonitorId + requestedMonitorId, ); if (!heartbeat.ping) { diff --git a/server/routers/status-page-router.js b/server/routers/status-page-router.js index 893f57564..54ee06018 100644 --- a/server/routers/status-page-router.js +++ b/server/routers/status-page-router.js @@ -2,7 +2,10 @@ let express = require("express"); const apicache = require("../modules/apicache"); const { UptimeKumaServer } = require("../uptime-kuma-server"); const StatusPage = require("../model/status_page"); -const { allowDevAllOrigin, sendHttpError } = require("../util-server"); +const { + allowDevAllOrigin, + sendHttpError, +} = require("../util-server"); const { R } = require("redbean-node"); const { badgeConstants } = require("../../src/util"); const { makeBadge } = require("badge-maker"); @@ -44,7 +47,7 @@ router.get("/api/status-page/:slug", cache("5 minutes"), async (request, respons try { // Get Status Page let statusPage = await R.findOne("status_page", " slug = ? ", [ - slug + slug, ]); if (!statusPage) { @@ -81,7 +84,7 @@ router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (reques AND public = 1 AND \`group\`.status_page_id = ? `, [ - statusPageID + statusPageID, ]); for (let monitorID of monitorIDList) { @@ -103,7 +106,7 @@ router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (reques response.json({ heartbeatList, - uptimeList + uptimeList, }); } catch (error) { @@ -120,7 +123,7 @@ router.get("/api/status-page/:slug/manifest.json", cache("1440 minutes"), async try { // Get Status Page let statusPage = await R.findOne("status_page", " slug = ? ", [ - slug + slug, ]); if (!statusPage) { @@ -137,9 +140,9 @@ router.get("/api/status-page/:slug/manifest.json", cache("1440 minutes"), async { "src": statusPage.icon, "sizes": "128x128", - "type": "image/png" - } - ] + "type": "image/png", + }, + ], }); } catch (error) { @@ -159,7 +162,7 @@ router.get("/api/status-page/:slug/badge", cache("5 minutes"), async (request, r downColor = badgeConstants.defaultDownColor, partialColor = "#F6BE00", maintenanceColor = "#808080", - style = badgeConstants.defaultStyle + style = badgeConstants.defaultStyle, } = request.query; try { @@ -169,7 +172,7 @@ router.get("/api/status-page/:slug/badge", cache("5 minutes"), async (request, r AND public = 1 AND \`group\`.status_page_id = ? `, [ - statusPageID + statusPageID, ]); let hasUp = false; diff --git a/server/server.js b/server/server.js index ec5ad49f6..90ce47922 100644 --- a/server/server.js +++ b/server/server.js @@ -37,13 +37,19 @@ if (!semver.satisfies(nodeVersion, requiredNodeVersions)) { } 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"); log.debug("server", "Arguments"); log.debug("server", args); -if (! process.env.NODE_ENV) { +if (!process.env.NODE_ENV) { process.env.NODE_ENV = "production"; } @@ -90,7 +96,16 @@ const Monitor = require("./model/monitor"); const User = require("./model/user"); 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"); log.debug("server", "Importing Notification"); @@ -101,8 +116,14 @@ log.debug("server", "Importing Database"); const Database = require("./database"); log.debug("server", "Importing Background Jobs"); -const { initBackgroundJobs, stopBackgroundJobs } = require("./jobs"); -const { loginRateLimiter, twoFaRateLimiter } = require("./rate-limiter"); +const { + initBackgroundJobs, + stopBackgroundJobs, +} = require("./jobs"); +const { + loginRateLimiter, + twoFaRateLimiter, +} = require("./rate-limiter"); const { apiAuth } = require("./auth"); const { login } = require("./auth"); @@ -122,7 +143,7 @@ const cloudflaredToken = args["cloudflared-token"] || process.env.UPTIME_KUMA_CL // 2FA / notp verification defaults const twoFAVerifyOptions = { "window": 1, - "time": 30 + "time": 30, }; /** @@ -132,13 +153,26 @@ const twoFAVerifyOptions = { const testMode = !!args["test"] || false; // 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 { databaseSocketHandler } = require("./socket-handlers/database-socket-handler"); const { remoteBrowserSocketHandler } = require("./socket-handlers/remote-browser-socket-handler"); const TwoFA = require("./2fa"); 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 { dockerSocketHandler } = require("./socket-handlers/docker-socket-handler"); const { maintenanceSocketHandler } = require("./socket-handlers/maintenance-socket-handler"); @@ -933,8 +967,9 @@ let needSetup = false; monitorID, socket.userID, ]); - const monitorData = [{ id: monitor.id, - active: monitor.active + const monitorData = [{ + id: monitor.id, + active: monitor.active, }]; const preloadData = await Monitor.preparePreloadData(monitorData); callback({ @@ -966,7 +1001,8 @@ let needSetup = false; SELECT * FROM heartbeat WHERE monitor_id = ? - AND time > ${sqlHourOffset} + AND time + > ${sqlHourOffset} ORDER BY time ASC `, [ monitorID, @@ -1519,7 +1555,7 @@ let needSetup = false; log.info("manage", `Clear Heartbeats Monitor: ${monitorID} User ID: ${socket.userID}`); await R.exec("DELETE FROM heartbeat WHERE monitor_id = ?", [ - monitorID + monitorID, ]); await sendHeartbeatList(socket, monitorID, true, true); @@ -1658,7 +1694,7 @@ async function checkOwner(userID, monitorID) { userID, ]); - if (! row) { + if (!row) { 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 // It should be run once only - if (! await Settings.get("initServerTimezone")) { + if (!await Settings.get("initServerTimezone")) { log.debug("server", "emit initServerTimezone"); socket.emit("initServerTimezone"); } @@ -1722,7 +1758,7 @@ async function initDatabase(testMode = false) { "jwtSecret", ]); - if (! jwtSecretBean) { + if (!jwtSecretBean) { log.info("server", "JWT secret is not found, generate one."); jwtSecretBean = await initJWTSecret(); log.info("server", "Stored JWT secret into database"); diff --git a/server/settings.js b/server/settings.js index 4776c554d..46dc48c0f 100644 --- a/server/settings.js +++ b/server/settings.js @@ -17,9 +17,7 @@ class Settings { * } * @type {{}} */ - static cacheList = { - - }; + static cacheList = {}; static cacheCleaner = null; @@ -61,7 +59,7 @@ class Settings { Settings.cacheList[key] = { value: v, - timestamp: Date.now() + timestamp: Date.now(), }; return v; @@ -129,7 +127,7 @@ class Settings { for (let key of keyList) { let bean = await R.findOne("setting", " `key` = ? ", [ - key + key, ]); if (bean == null) { diff --git a/server/util-server.js b/server/util-server.js index 5ebc62ac5..8e0fd5aa6 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -1,7 +1,11 @@ const tcpp = require("tcp-ping"); const ping = require("@louislam/ping"); 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 { Resolver } = require("dns"); const iconv = require("iconv-lite"); @@ -22,14 +26,20 @@ const tls = require("tls"); const { dictionaries: { - rfc2865: { file, attributes }, + rfc2865: { + file, + attributes, + }, }, } = require("node-radius-utils"); const dayjs = require("dayjs"); // SASLOptions used in JSDoc -// eslint-disable-next-line no-unused-vars -const { Kafka, SASLOptions } = require("kafkajs"); +const { + Kafka, + // eslint-disable-next-line no-unused-vars + SASLOptions, +} = require("kafkajs"); const crypto = require("crypto"); const isWindows = process.platform === /^win/.test(process.platform); @@ -75,7 +85,7 @@ exports.getOidcTokenClientCredentials = async (tokenEndpoint, clientId, clientSe let client = new oauthProvider.Client({ client_id: clientId, client_secret: clientSecret, - token_endpoint_auth_method: authMethod + token_endpoint_auth_method: authMethod, }); // 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 = {}) { 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; @@ -213,7 +228,7 @@ exports.kafkaProducerAsync = function (brokers, topic, message, options = {}, sa allowAutoTopicCreation: allowAutoTopicCreation, retry: { retries: 0, - } + }, }); producer.connect().then( @@ -234,14 +249,14 @@ exports.kafkaProducerAsync = function (brokers, topic, message, options = {}, sa connectedToKafka = true; clearTimeout(timeoutID); }); - } + }, ).catch( (e) => { connectedToKafka = true; producer.disconnect(); clearTimeout(timeoutID); reject(new Error("Error in producer connection: " + e.message)); - } + }, ); producer.on("producer.network.request_timeout", (_) => { @@ -409,7 +424,7 @@ exports.mysqlQuery = function (connectionString, query, password = undefined) { return new Promise((resolve, reject) => { const connection = mysql.createConnection({ uri: connectionString, - password + password, }); connection.on("error", (err) => { @@ -494,8 +509,8 @@ exports.redisPingAsync = function (dsn, rejectUnauthorized) { const client = redis.createClient({ url: dsn, socket: { - rejectUnauthorized - } + rejectUnauthorized, + }, }); client.on("error", (err) => { if (client.isOpen) { @@ -661,7 +676,7 @@ exports.checkCertificate = function (socket) { return { valid: valid, - certInfo: parsedInfo + certInfo: parsedInfo, }; }; @@ -693,7 +708,7 @@ exports.checkStatusCode = function (status, acceptedCodes) { } } else { log.error("monitor", `${codeRange} is not a valid status code range`); - continue; + } } @@ -925,14 +940,21 @@ module.exports.timeObjectToLocal = (obj, timezone = undefined) => { * @returns {Promise} Result of gRPC query */ 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 protoServiceObject = protocObject.root.lookupService(grpcServiceName); const Client = grpc.makeGenericClientConstructor({}); const credentials = grpcEnableTls ? grpc.credentials.createSsl() : grpc.credentials.createInsecure(); const client = new Client( grpcUrl, - credentials + credentials, ); const grpcService = protoServiceObject.create(function (method, requestData, cb) { const fullServiceName = method.fullName; @@ -955,14 +977,14 @@ module.exports.grpcQuery = async (options) => { return resolve({ code: err.code, errorMessage: err.details, - data: "" + data: "", }); } else { log.debug("monitor:", `gRPC response: ${JSON.stringify(response)}`); return resolve({ code: 1, errorMessage: "", - data: responseData + data: responseData, }); } }); @@ -970,7 +992,7 @@ module.exports.grpcQuery = async (options) => { return resolve({ 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`, - data: "" + data: "", }); } diff --git a/server/utils/array-with-key.js b/server/utils/array-with-key.js index 94afc792a..1adbd9f9b 100644 --- a/server/utils/array-with-key.js +++ b/server/utils/array-with-key.js @@ -81,5 +81,5 @@ class ArrayWithKey { } module.exports = { - ArrayWithKey + ArrayWithKey, }; diff --git a/server/utils/limit-queue.js b/server/utils/limit-queue.js index 9da6d410e..023f26e28 100644 --- a/server/utils/limit-queue.js +++ b/server/utils/limit-queue.js @@ -44,5 +44,5 @@ class LimitQueue extends ArrayWithKey { } module.exports = { - LimitQueue + LimitQueue, }; diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue index f6d728029..d97e033a6 100644 --- a/src/components/NotificationDialog.vue +++ b/src/components/NotificationDialog.vue @@ -121,6 +121,7 @@ export default { "Elks": "46elks", "GoogleChat": "Google Chat (Google Workspace)", "gorush": "Gorush", + "GovNotify": "GOV Notify", "gotify": "Gotify", "GrafanaOncall": "Grafana Oncall", "HeiiOnCall": "Heii On-Call", @@ -262,7 +263,7 @@ export default { this.id = null; this.notification = { name: "", - type: "telegram", + type: "GovNotify", isDefault: false, }; } diff --git a/src/components/notifications/GovNotify.vue b/src/components/notifications/GovNotify.vue new file mode 100644 index 000000000..d377dae1e --- /dev/null +++ b/src/components/notifications/GovNotify.vue @@ -0,0 +1,75 @@ + + diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js index efa2af5c4..76df2af65 100644 --- a/src/components/notifications/index.js +++ b/src/components/notifications/index.js @@ -67,6 +67,7 @@ import Cellsynt from "./Cellsynt.vue"; import WPush from "./WPush.vue"; import SIGNL4 from "./SIGNL4.vue"; import SendGrid from "./SendGrid.vue"; +import GovNotify from "./GovNotify.vue"; /** * Manage all notification form. @@ -142,6 +143,7 @@ const NotificationFormList = { "Cellsynt": Cellsynt, "WPush": WPush, "SendGrid": SendGrid, + "GovNotify": GovNotify }; export default NotificationFormList;