pull/5660/merge
Toby Liddicoat 2025-03-07 22:55:25 +00:00 committed by GitHub
commit 62892a56eb
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",
"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",

View File

@ -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",

View File

@ -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");

View File

@ -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,
]);

View File

@ -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,

View File

@ -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,
};
}
}

View File

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

View File

@ -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();
}

View File

@ -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,
]);
}

View File

@ -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<string>} 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,
]);
}

View File

@ -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;

View File

@ -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:
* <code>
* app.get('/api/cache/performance', (req, res) => {
* res.json(apicache.getPerformance())
* })
* </code>
* @returns {any[]}
*/
* Return cache performance statistics (hit rate). Suitable for
* putting into a route:
* <code>
* app.get('/api/cache/performance', (req, res) => {
* res.json(apicache.getPerformance())
* })
* </code>
* @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) {

View File

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

View File

@ -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) {

View File

@ -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
//# sourceMappingURL=flags.js.map

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
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
//# 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
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
//# sourceMappingURL=ntlm.js.map

View File

@ -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
//# 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
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;
}

View File

@ -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);

View File

@ -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),
];
/**

View File

@ -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", () => {

View File

@ -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 = {

View File

@ -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";

View File

@ -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;

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",
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;

View File

@ -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;

View File

@ -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,
};

View File

@ -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,
};
}

View File

@ -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) {

View File

@ -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;

View File

@ -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");

View File

@ -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) {

View File

@ -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<object>} 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: "",
});
}

View File

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

View File

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

View File

@ -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,
};
}

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 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;