mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-03-05 20:59:48 -08:00
Merge 62ced29d7f
into c7d7fdd632
This commit is contained in:
commit
a9b71ce8ca
26
package-lock.json
generated
26
package-lock.json
generated
|
@ -61,6 +61,7 @@
|
||||||
"node-radius-client": "~1.0.0",
|
"node-radius-client": "~1.0.0",
|
||||||
"nodemailer": "~6.9.13",
|
"nodemailer": "~6.9.13",
|
||||||
"nostr-tools": "^2.10.4",
|
"nostr-tools": "^2.10.4",
|
||||||
|
"notifications-node-client": "^8.2.1",
|
||||||
"notp": "~2.0.3",
|
"notp": "~2.0.3",
|
||||||
"openid-client": "^5.4.2",
|
"openid-client": "^5.4.2",
|
||||||
"password-hash": "~1.2.2",
|
"password-hash": "~1.2.2",
|
||||||
|
@ -12732,6 +12733,31 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/notifications-node-client": {
|
||||||
|
"version": "8.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/notifications-node-client/-/notifications-node-client-8.2.1.tgz",
|
||||||
|
"integrity": "sha512-wyZh/NbjN8S2uQX18utYtCyC726BBaGeTc4HeUpdhZv5sYKuaQY94N31v9syh8SzVgehyMzW37y08EePmi+k3Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.7.2",
|
||||||
|
"jsonwebtoken": "^9.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17.3",
|
||||||
|
"npm": ">=6.14.13"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/notifications-node-client/node_modules/axios": {
|
||||||
|
"version": "1.7.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
|
||||||
|
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.6",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/notp": {
|
"node_modules/notp": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/notp/-/notp-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/notp/-/notp-2.0.3.tgz",
|
||||||
|
|
|
@ -119,6 +119,7 @@
|
||||||
"node-radius-client": "~1.0.0",
|
"node-radius-client": "~1.0.0",
|
||||||
"nodemailer": "~6.9.13",
|
"nodemailer": "~6.9.13",
|
||||||
"nostr-tools": "^2.10.4",
|
"nostr-tools": "^2.10.4",
|
||||||
|
"notifications-node-client": "^8.2.1",
|
||||||
"notp": "~2.0.3",
|
"notp": "~2.0.3",
|
||||||
"openid-client": "^5.4.2",
|
"openid-client": "^5.4.2",
|
||||||
"password-hash": "~1.2.2",
|
"password-hash": "~1.2.2",
|
||||||
|
|
|
@ -3,7 +3,10 @@ const passwordHash = require("./password-hash");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { setting } = require("./util-server");
|
const { setting } = require("./util-server");
|
||||||
const { log } = require("../src/util");
|
const { log } = require("../src/util");
|
||||||
const { loginRateLimiter, apiRateLimiter } = require("./rate-limiter");
|
const {
|
||||||
|
loginRateLimiter,
|
||||||
|
apiRateLimiter,
|
||||||
|
} = require("./rate-limiter");
|
||||||
const { Settings } = require("./settings");
|
const { Settings } = require("./settings");
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
|
|
||||||
|
|
|
@ -47,10 +47,11 @@ async function sendNotificationList(socket) {
|
||||||
*/
|
*/
|
||||||
async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite = false) {
|
async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite = false) {
|
||||||
let list = await R.getAll(`
|
let list = await R.getAll(`
|
||||||
SELECT * FROM heartbeat
|
SELECT *
|
||||||
|
FROM heartbeat
|
||||||
WHERE monitor_id = ?
|
WHERE monitor_id = ?
|
||||||
ORDER BY time DESC
|
ORDER BY time DESC
|
||||||
LIMIT 100
|
LIMIT 100
|
||||||
`, [
|
`, [
|
||||||
monitorID,
|
monitorID,
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { setSetting, setting } = require("./util-server");
|
const {
|
||||||
const { log, sleep } = require("../src/util");
|
setSetting,
|
||||||
|
setting,
|
||||||
|
} = require("./util-server");
|
||||||
|
const {
|
||||||
|
log,
|
||||||
|
sleep,
|
||||||
|
} = require("../src/util");
|
||||||
const knex = require("knex");
|
const knex = require("knex");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { EmbeddedMariaDB } = require("./embedded-mariadb");
|
const { EmbeddedMariaDB } = require("./embedded-mariadb");
|
||||||
|
@ -136,24 +142,24 @@ class Database {
|
||||||
Database.dataDir = process.env.DATA_DIR || args["data-dir"] || "./data/";
|
Database.dataDir = process.env.DATA_DIR || args["data-dir"] || "./data/";
|
||||||
|
|
||||||
Database.sqlitePath = path.join(Database.dataDir, "kuma.db");
|
Database.sqlitePath = path.join(Database.dataDir, "kuma.db");
|
||||||
if (! fs.existsSync(Database.dataDir)) {
|
if (!fs.existsSync(Database.dataDir)) {
|
||||||
fs.mkdirSync(Database.dataDir, { recursive: true });
|
fs.mkdirSync(Database.dataDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
Database.uploadDir = path.join(Database.dataDir, "upload/");
|
Database.uploadDir = path.join(Database.dataDir, "upload/");
|
||||||
|
|
||||||
if (! fs.existsSync(Database.uploadDir)) {
|
if (!fs.existsSync(Database.uploadDir)) {
|
||||||
fs.mkdirSync(Database.uploadDir, { recursive: true });
|
fs.mkdirSync(Database.uploadDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create screenshot dir
|
// Create screenshot dir
|
||||||
Database.screenshotDir = path.join(Database.dataDir, "screenshots/");
|
Database.screenshotDir = path.join(Database.dataDir, "screenshots/");
|
||||||
if (! fs.existsSync(Database.screenshotDir)) {
|
if (!fs.existsSync(Database.screenshotDir)) {
|
||||||
fs.mkdirSync(Database.screenshotDir, { recursive: true });
|
fs.mkdirSync(Database.screenshotDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
Database.dockerTLSDir = path.join(Database.dataDir, "docker-tls/");
|
Database.dockerTLSDir = path.join(Database.dataDir, "docker-tls/");
|
||||||
if (! fs.existsSync(Database.dockerTLSDir)) {
|
if (!fs.existsSync(Database.dockerTLSDir)) {
|
||||||
fs.mkdirSync(Database.dockerTLSDir, { recursive: true });
|
fs.mkdirSync(Database.dockerTLSDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,7 +237,7 @@ class Database {
|
||||||
|
|
||||||
if (dbConfig.type === "sqlite") {
|
if (dbConfig.type === "sqlite") {
|
||||||
|
|
||||||
if (! fs.existsSync(Database.sqlitePath)) {
|
if (!fs.existsSync(Database.sqlitePath)) {
|
||||||
log.info("server", "Copying Database");
|
log.info("server", "Copying Database");
|
||||||
fs.copyFileSync(Database.templatePath, Database.sqlitePath);
|
fs.copyFileSync(Database.templatePath, Database.sqlitePath);
|
||||||
}
|
}
|
||||||
|
@ -252,7 +258,7 @@ class Database {
|
||||||
idleTimeoutMillis: 120 * 1000,
|
idleTimeoutMillis: 120 * 1000,
|
||||||
propagateCreateError: false,
|
propagateCreateError: false,
|
||||||
acquireTimeoutMillis: acquireConnectionTimeout,
|
acquireTimeoutMillis: acquireConnectionTimeout,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
} else if (dbConfig.type === "mariadb") {
|
} else if (dbConfig.type === "mariadb") {
|
||||||
if (!/^\w+$/.test(dbConfig.dbName)) {
|
if (!/^\w+$/.test(dbConfig.dbName)) {
|
||||||
|
@ -451,7 +457,7 @@ class Database {
|
||||||
static async patchSqlite() {
|
static async patchSqlite() {
|
||||||
let version = parseInt(await setting("database_version"));
|
let version = parseInt(await setting("database_version"));
|
||||||
|
|
||||||
if (! version) {
|
if (!version) {
|
||||||
version = 0;
|
version = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -502,7 +508,7 @@ class Database {
|
||||||
log.debug("db", "Database Patch 2.0 Process");
|
log.debug("db", "Database Patch 2.0 Process");
|
||||||
let databasePatchedFiles = await setting("databasePatchedFiles");
|
let databasePatchedFiles = await setting("databasePatchedFiles");
|
||||||
|
|
||||||
if (! databasePatchedFiles) {
|
if (!databasePatchedFiles) {
|
||||||
databasePatchedFiles = {};
|
databasePatchedFiles = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -579,11 +585,11 @@ class Database {
|
||||||
let id = await R.store(statusPage);
|
let id = await R.store(statusPage);
|
||||||
|
|
||||||
await R.exec("UPDATE incident SET status_page_id = ? WHERE status_page_id IS NULL", [
|
await R.exec("UPDATE incident SET status_page_id = ? WHERE status_page_id IS NULL", [
|
||||||
id
|
id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await R.exec("UPDATE [group] SET status_page_id = ? WHERE status_page_id IS NULL", [
|
await R.exec("UPDATE [group] SET status_page_id = ? WHERE status_page_id IS NULL", [
|
||||||
id
|
id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await R.exec("DELETE FROM setting WHERE type = 'statusPage'");
|
await R.exec("DELETE FROM setting WHERE type = 'statusPage'");
|
||||||
|
@ -611,13 +617,13 @@ class Database {
|
||||||
static async patch2Recursion(sqlFilename, databasePatchedFiles) {
|
static async patch2Recursion(sqlFilename, databasePatchedFiles) {
|
||||||
let value = this.patchList[sqlFilename];
|
let value = this.patchList[sqlFilename];
|
||||||
|
|
||||||
if (! value) {
|
if (!value) {
|
||||||
log.info("db", sqlFilename + " skip");
|
log.info("db", sqlFilename + " skip");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if patched
|
// Check if patched
|
||||||
if (! databasePatchedFiles[sqlFilename]) {
|
if (!databasePatchedFiles[sqlFilename]) {
|
||||||
log.info("db", sqlFilename + " is not patched");
|
log.info("db", sqlFilename + " is not patched");
|
||||||
|
|
||||||
if (value.parents) {
|
if (value.parents) {
|
||||||
|
@ -652,7 +658,7 @@ class Database {
|
||||||
// Remove all comments (--)
|
// Remove all comments (--)
|
||||||
let lines = text.split("\n");
|
let lines = text.split("\n");
|
||||||
lines = lines.filter((line) => {
|
lines = lines.filter((line) => {
|
||||||
return ! line.startsWith("--");
|
return !line.startsWith("--");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Split statements by semicolon
|
// Split statements by semicolon
|
||||||
|
@ -797,7 +803,8 @@ class Database {
|
||||||
|
|
||||||
// Stop if stat_* tables are not empty
|
// Stop if stat_* tables are not empty
|
||||||
for (let table of [ "stat_minutely", "stat_hourly", "stat_daily" ]) {
|
for (let table of [ "stat_minutely", "stat_hourly", "stat_daily" ]) {
|
||||||
let countResult = await R.getRow(`SELECT COUNT(*) AS count FROM ${table}`);
|
let countResult = await R.getRow(`SELECT COUNT(*) AS count
|
||||||
|
FROM ${table}`);
|
||||||
let count = countResult.count;
|
let count = countResult.count;
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
log.warn("db", `Aggregate table ${table} is not empty, migration will not be started (Maybe you were using 2.0.0-dev?)`);
|
log.warn("db", `Aggregate table ${table} is not empty, migration will not be started (Maybe you were using 2.0.0-dev?)`);
|
||||||
|
@ -814,12 +821,12 @@ class Database {
|
||||||
for (let monitor of monitors) {
|
for (let monitor of monitors) {
|
||||||
// Get a list of unique dates from the heartbeat table, using raw sql
|
// Get a list of unique dates from the heartbeat table, using raw sql
|
||||||
let dates = await R.getAll(`
|
let dates = await R.getAll(`
|
||||||
SELECT DISTINCT DATE(time) AS date
|
SELECT DISTINCT DATE (time) AS date
|
||||||
FROM heartbeat
|
FROM heartbeat
|
||||||
WHERE monitor_id = ?
|
WHERE monitor_id = ?
|
||||||
ORDER BY date ASC
|
ORDER BY date ASC
|
||||||
`, [
|
`, [
|
||||||
monitor.monitor_id
|
monitor.monitor_id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
for (let date of dates) {
|
for (let date of dates) {
|
||||||
|
@ -833,7 +840,7 @@ class Database {
|
||||||
SELECT status, ping, time
|
SELECT status, ping, time
|
||||||
FROM heartbeat
|
FROM heartbeat
|
||||||
WHERE monitor_id = ?
|
WHERE monitor_id = ?
|
||||||
AND DATE(time) = ?
|
AND DATE (time) = ?
|
||||||
ORDER BY time ASC
|
ORDER BY time ASC
|
||||||
`, [ monitor.monitor_id, date.date ]);
|
`, [ monitor.monitor_id, date.date ]);
|
||||||
|
|
||||||
|
@ -887,19 +894,21 @@ class Database {
|
||||||
log.info("db", "Deleting non-important heartbeats for monitor " + monitor.id);
|
log.info("db", "Deleting non-important heartbeats for monitor " + monitor.id);
|
||||||
}
|
}
|
||||||
await R.exec(`
|
await R.exec(`
|
||||||
DELETE FROM heartbeat
|
DELETE
|
||||||
|
FROM heartbeat
|
||||||
WHERE monitor_id = ?
|
WHERE monitor_id = ?
|
||||||
AND important = 0
|
AND important = 0
|
||||||
AND time < ${sqlHourOffset}
|
AND time
|
||||||
AND id NOT IN (
|
< ${sqlHourOffset}
|
||||||
|
AND id NOT IN (
|
||||||
SELECT id FROM ( -- written this way for Maria's support
|
SELECT id FROM ( -- written this way for Maria's support
|
||||||
SELECT id
|
SELECT id
|
||||||
FROM heartbeat
|
FROM heartbeat
|
||||||
WHERE monitor_id = ?
|
WHERE monitor_id = ?
|
||||||
ORDER BY time DESC
|
ORDER BY time DESC
|
||||||
LIMIT ?
|
LIMIT ?
|
||||||
) AS limited_ids
|
) AS limited_ids
|
||||||
)
|
)
|
||||||
`, [
|
`, [
|
||||||
monitor.id,
|
monitor.id,
|
||||||
-24,
|
-24,
|
||||||
|
|
|
@ -146,7 +146,7 @@ class DockerHost {
|
||||||
static getHttpsAgentOptions(dockerType, url) {
|
static getHttpsAgentOptions(dockerType, url) {
|
||||||
let baseOptions = {
|
let baseOptions = {
|
||||||
maxCachedSessions: 0,
|
maxCachedSessions: 0,
|
||||||
rejectUnauthorized: true
|
rejectUnauthorized: true,
|
||||||
};
|
};
|
||||||
let certOptions = {};
|
let certOptions = {};
|
||||||
|
|
||||||
|
@ -163,13 +163,13 @@ class DockerHost {
|
||||||
certOptions = {
|
certOptions = {
|
||||||
ca,
|
ca,
|
||||||
key,
|
key,
|
||||||
cert
|
cert,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...baseOptions,
|
...baseOptions,
|
||||||
...certOptions
|
...certOptions,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ const jobs = [
|
||||||
interval: "*/5 * * * *",
|
interval: "*/5 * * * *",
|
||||||
jobFunc: incrementalVacuum,
|
jobFunc: incrementalVacuum,
|
||||||
croner: null,
|
croner: null,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,5 +54,5 @@ const stopBackgroundJobs = function () {
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
initBackgroundJobs,
|
initBackgroundJobs,
|
||||||
stopBackgroundJobs
|
stopBackgroundJobs,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||||
const { parseTimeObject, parseTimeFromTimeObject, log } = require("../../src/util");
|
const {
|
||||||
|
parseTimeObject,
|
||||||
|
parseTimeFromTimeObject,
|
||||||
|
log,
|
||||||
|
} = require("../../src/util");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
const Cron = require("croner");
|
const Cron = require("croner");
|
||||||
|
@ -192,7 +196,8 @@ class Maintenance extends BeanModel {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
static validateCron(cron) {
|
static validateCron(cron) {
|
||||||
let job = new Cron(cron, () => {});
|
let job = new Cron(cron, () => {
|
||||||
|
});
|
||||||
job.stop();
|
job.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,37 @@
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const { Prometheus } = require("../prometheus");
|
const { Prometheus } = require("../prometheus");
|
||||||
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND,
|
const {
|
||||||
SQL_DATETIME_FORMAT, evaluateJsonQuery
|
log,
|
||||||
|
UP,
|
||||||
|
DOWN,
|
||||||
|
PENDING,
|
||||||
|
MAINTENANCE,
|
||||||
|
flipStatus,
|
||||||
|
MAX_INTERVAL_SECOND,
|
||||||
|
MIN_INTERVAL_SECOND,
|
||||||
|
SQL_DATETIME_FORMAT,
|
||||||
|
evaluateJsonQuery,
|
||||||
} = require("../../src/util");
|
} = require("../../src/util");
|
||||||
const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius, grpcQuery,
|
const {
|
||||||
redisPingAsync, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal
|
tcping,
|
||||||
|
ping,
|
||||||
|
checkCertificate,
|
||||||
|
checkStatusCode,
|
||||||
|
getTotalClientInRoom,
|
||||||
|
setting,
|
||||||
|
mssqlQuery,
|
||||||
|
postgresQuery,
|
||||||
|
mysqlQuery,
|
||||||
|
setSetting,
|
||||||
|
httpNtlm,
|
||||||
|
radius,
|
||||||
|
grpcQuery,
|
||||||
|
redisPingAsync,
|
||||||
|
kafkaProducerAsync,
|
||||||
|
getOidcTokenClientCredentials,
|
||||||
|
rootCertificatesFingerprints,
|
||||||
|
axiosAbortSignal,
|
||||||
} = require("../util-server");
|
} = require("../util-server");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||||
|
@ -61,7 +87,10 @@ class Monitor extends BeanModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (certExpiry && (this.type === "http" || this.type === "keyword" || this.type === "json-query") && this.getURLProtocol() === "https:") {
|
if (certExpiry && (this.type === "http" || this.type === "keyword" || this.type === "json-query") && this.getURLProtocol() === "https:") {
|
||||||
const { certExpiryDaysRemaining, validCert } = await this.getCertExpiry(this.id);
|
const {
|
||||||
|
certExpiryDaysRemaining,
|
||||||
|
validCert,
|
||||||
|
} = await this.getCertExpiry(this.id);
|
||||||
obj.certExpiryDaysRemaining = certExpiryDaysRemaining;
|
obj.certExpiryDaysRemaining = certExpiryDaysRemaining;
|
||||||
obj.validCert = validCert;
|
obj.validCert = validCert;
|
||||||
}
|
}
|
||||||
|
@ -218,13 +247,13 @@ class Monitor extends BeanModel {
|
||||||
if (tlsInfo?.valid && tlsInfo?.certInfo?.daysRemaining) {
|
if (tlsInfo?.valid && tlsInfo?.certInfo?.daysRemaining) {
|
||||||
return {
|
return {
|
||||||
certExpiryDaysRemaining: tlsInfo.certInfo.daysRemaining,
|
certExpiryDaysRemaining: tlsInfo.certInfo.daysRemaining,
|
||||||
validCert: true
|
validCert: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
certExpiryDaysRemaining: "",
|
certExpiryDaysRemaining: "",
|
||||||
validCert: false
|
validCert: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -334,7 +363,7 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
let beatInterval = this.interval;
|
let beatInterval = this.interval;
|
||||||
|
|
||||||
if (! beatInterval) {
|
if (!beatInterval) {
|
||||||
beatInterval = 1;
|
beatInterval = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -479,7 +508,7 @@ class Monitor extends BeanModel {
|
||||||
...(contentType ? { "Content-Type": contentType } : {}),
|
...(contentType ? { "Content-Type": contentType } : {}),
|
||||||
...(basicAuthHeader),
|
...(basicAuthHeader),
|
||||||
...(oauth2AuthHeader),
|
...(oauth2AuthHeader),
|
||||||
...(this.headers ? JSON.parse(this.headers) : {})
|
...(this.headers ? JSON.parse(this.headers) : {}),
|
||||||
},
|
},
|
||||||
maxRedirects: this.maxredirects,
|
maxRedirects: this.maxredirects,
|
||||||
validateStatus: (status) => {
|
validateStatus: (status) => {
|
||||||
|
@ -504,7 +533,10 @@ class Monitor extends BeanModel {
|
||||||
const proxy = await R.load("proxy", this.proxy_id);
|
const proxy = await R.load("proxy", this.proxy_id);
|
||||||
|
|
||||||
if (proxy && proxy.active) {
|
if (proxy && proxy.active) {
|
||||||
const { httpAgent, httpsAgent } = Proxy.createAgents(proxy, {
|
const {
|
||||||
|
httpAgent,
|
||||||
|
httpsAgent,
|
||||||
|
} = Proxy.createAgents(proxy, {
|
||||||
httpsAgentOptions: httpsAgentOptions,
|
httpsAgentOptions: httpsAgentOptions,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -518,7 +550,7 @@ class Monitor extends BeanModel {
|
||||||
let jar = new CookieJar();
|
let jar = new CookieJar();
|
||||||
let httpsCookieAgentOptions = {
|
let httpsCookieAgentOptions = {
|
||||||
...httpsAgentOptions,
|
...httpsAgentOptions,
|
||||||
cookies: { jar }
|
cookies: { jar },
|
||||||
};
|
};
|
||||||
options.httpsAgent = new HttpsCookieAgent(httpsCookieAgentOptions);
|
options.httpsAgent = new HttpsCookieAgent(httpsCookieAgentOptions);
|
||||||
}
|
}
|
||||||
|
@ -600,7 +632,10 @@ class Monitor extends BeanModel {
|
||||||
} else if (this.type === "json-query") {
|
} else if (this.type === "json-query") {
|
||||||
let data = res.data;
|
let data = res.data;
|
||||||
|
|
||||||
const { status, response } = await evaluateJsonQuery(data, this.jsonPath, this.jsonPathOperator, this.expectedValue);
|
const {
|
||||||
|
status,
|
||||||
|
response,
|
||||||
|
} = await evaluateJsonQuery(data, this.jsonPath, this.jsonPathOperator, this.expectedValue);
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
bean.status = UP;
|
bean.status = UP;
|
||||||
|
@ -681,7 +716,7 @@ class Monitor extends BeanModel {
|
||||||
params: {
|
params: {
|
||||||
filter: filter,
|
filter: filter,
|
||||||
key: steamAPIKey,
|
key: steamAPIKey,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.data.response && res.data.response.servers && res.data.response.servers.length > 0) {
|
if (res.data.response && res.data.response.servers && res.data.response.servers.length > 0) {
|
||||||
|
@ -690,7 +725,8 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
bean.ping = await ping(this.hostname, this.packetSize);
|
bean.ping = await ping(this.hostname, this.packetSize);
|
||||||
} catch (_) { }
|
} catch (_) {
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Server not found on Steam");
|
throw new Error("Server not found on Steam");
|
||||||
}
|
}
|
||||||
|
@ -739,7 +775,7 @@ class Monitor extends BeanModel {
|
||||||
} else if (dockerHost._dockerType === "tcp") {
|
} else if (dockerHost._dockerType === "tcp") {
|
||||||
options.baseURL = DockerHost.patchDockerURL(dockerHost._dockerDaemon);
|
options.baseURL = DockerHost.patchDockerURL(dockerHost._dockerDaemon);
|
||||||
options.httpsAgent = new https.Agent(
|
options.httpsAgent = new https.Agent(
|
||||||
DockerHost.getHttpsAgentOptions(dockerHost._dockerType, options.baseURL)
|
DockerHost.getHttpsAgentOptions(dockerHost._dockerType, options.baseURL),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -984,12 +1020,12 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
previousBeat = bean;
|
previousBeat = bean;
|
||||||
|
|
||||||
if (! this.isStop) {
|
if (!this.isStop) {
|
||||||
log.debug("monitor", `[${this.name}] SetTimeout for next check.`);
|
log.debug("monitor", `[${this.name}] SetTimeout for next check.`);
|
||||||
|
|
||||||
let intervalRemainingMs = Math.max(
|
let intervalRemainingMs = Math.max(
|
||||||
1,
|
1,
|
||||||
beatInterval * 1000 - dayjs().diff(dayjs.utc(bean.time))
|
beatInterval * 1000 - dayjs().diff(dayjs.utc(bean.time)),
|
||||||
);
|
);
|
||||||
|
|
||||||
log.debug("monitor", `[${this.name}] Next heartbeat in: ${intervalRemainingMs}ms`);
|
log.debug("monitor", `[${this.name}] Next heartbeat in: ${intervalRemainingMs}ms`);
|
||||||
|
@ -1013,7 +1049,7 @@ class Monitor extends BeanModel {
|
||||||
UptimeKumaServer.errorLog(e, false);
|
UptimeKumaServer.errorLog(e, false);
|
||||||
log.error("monitor", "Please report to https://github.com/louislam/uptime-kuma/issues");
|
log.error("monitor", "Please report to https://github.com/louislam/uptime-kuma/issues");
|
||||||
|
|
||||||
if (! this.isStop) {
|
if (!this.isStop) {
|
||||||
log.info("monitor", "Try to restart the monitor");
|
log.info("monitor", "Try to restart the monitor");
|
||||||
this.heartbeatInterval = setTimeout(safeBeat, this.interval * 1000);
|
this.heartbeatInterval = setTimeout(safeBeat, this.interval * 1000);
|
||||||
}
|
}
|
||||||
|
@ -1047,7 +1083,7 @@ class Monitor extends BeanModel {
|
||||||
username: this.basic_auth_user,
|
username: this.basic_auth_user,
|
||||||
password: this.basic_auth_pass,
|
password: this.basic_auth_pass,
|
||||||
domain: this.authDomain,
|
domain: this.authDomain,
|
||||||
workstation: this.authWorkstation ? this.authWorkstation : undefined
|
workstation: this.authWorkstation ? this.authWorkstation : undefined,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
res = await axios.request(options);
|
res = await axios.request(options);
|
||||||
|
@ -1065,8 +1101,9 @@ class Monitor extends BeanModel {
|
||||||
let oauth2AuthHeader = {
|
let oauth2AuthHeader = {
|
||||||
"Authorization": this.oauthAccessToken.token_type + " " + this.oauthAccessToken.access_token,
|
"Authorization": this.oauthAccessToken.token_type + " " + this.oauthAccessToken.access_token,
|
||||||
};
|
};
|
||||||
options.headers = { ...(options.headers),
|
options.headers = {
|
||||||
...(oauth2AuthHeader)
|
...(options.headers),
|
||||||
|
...(oauth2AuthHeader),
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.makeAxiosRequest(options, true);
|
return this.makeAxiosRequest(options, true);
|
||||||
|
@ -1158,7 +1195,7 @@ class Monitor extends BeanModel {
|
||||||
if (oldCertInfo.certInfo.fingerprint256 !== checkCertificateResult.certInfo.fingerprint256) {
|
if (oldCertInfo.certInfo.fingerprint256 !== checkCertificateResult.certInfo.fingerprint256) {
|
||||||
log.debug("monitor", "Resetting sent_history");
|
log.debug("monitor", "Resetting sent_history");
|
||||||
await R.exec("DELETE FROM notification_sent_history WHERE type = 'certificate' AND monitor_id = ?", [
|
await R.exec("DELETE FROM notification_sent_history WHERE type = 'certificate' AND monitor_id = ?", [
|
||||||
this.id
|
this.id,
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
log.debug("monitor", "No need to reset sent_history");
|
log.debug("monitor", "No need to reset sent_history");
|
||||||
|
@ -1168,7 +1205,8 @@ class Monitor extends BeanModel {
|
||||||
} else {
|
} else {
|
||||||
log.debug("monitor", "Not valid object");
|
log.debug("monitor", "Not valid object");
|
||||||
}
|
}
|
||||||
} catch (e) { }
|
} catch (e) {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1326,8 +1364,9 @@ class Monitor extends BeanModel {
|
||||||
for (let notification of notificationList) {
|
for (let notification of notificationList) {
|
||||||
try {
|
try {
|
||||||
const heartbeatJSON = bean.toJSON();
|
const heartbeatJSON = bean.toJSON();
|
||||||
const monitorData = [{ id: monitor.id,
|
const monitorData = [{
|
||||||
active: monitor.active
|
id: monitor.id,
|
||||||
|
active: monitor.active,
|
||||||
}];
|
}];
|
||||||
const preloadData = await Monitor.preparePreloadData(monitorData);
|
const preloadData = await Monitor.preparePreloadData(monitorData);
|
||||||
// Prevent if the msg is undefined, notifications such as Discord cannot send out.
|
// Prevent if the msg is undefined, notifications such as Discord cannot send out.
|
||||||
|
@ -1370,7 +1409,7 @@ class Monitor extends BeanModel {
|
||||||
if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) {
|
if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) {
|
||||||
const notificationList = await Monitor.getNotificationList(this);
|
const notificationList = await Monitor.getNotificationList(this);
|
||||||
|
|
||||||
if (! notificationList.length > 0) {
|
if (!notificationList.length > 0) {
|
||||||
// fail fast. If no notification is set, all the following checks can be skipped.
|
// fail fast. If no notification is set, all the following checks can be skipped.
|
||||||
log.debug("monitor", "No notification, no need to send cert notification");
|
log.debug("monitor", "No notification, no need to send cert notification");
|
||||||
return;
|
return;
|
||||||
|
@ -1458,7 +1497,7 @@ class Monitor extends BeanModel {
|
||||||
*/
|
*/
|
||||||
static async getPreviousHeartbeat(monitorID) {
|
static async getPreviousHeartbeat(monitorID) {
|
||||||
return await R.findOne("heartbeat", " id = (select MAX(id) from heartbeat where monitor_id = ?)", [
|
return await R.findOne("heartbeat", " id = (select MAX(id) from heartbeat where monitor_id = ?)", [
|
||||||
monitorID
|
monitorID,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1570,7 +1609,7 @@ class Monitor extends BeanModel {
|
||||||
monitor_id: row.monitor_id,
|
monitor_id: row.monitor_id,
|
||||||
value: row.value,
|
value: row.value,
|
||||||
name: row.name,
|
name: row.name,
|
||||||
color: row.color
|
color: row.color,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1687,7 +1726,7 @@ class Monitor extends BeanModel {
|
||||||
*/
|
*/
|
||||||
static async unlinkAllChildren(groupID) {
|
static async unlinkAllChildren(groupID) {
|
||||||
return await R.exec("UPDATE `monitor` SET parent = ? WHERE parent = ? ", [
|
return await R.exec("UPDATE `monitor` SET parent = ? WHERE parent = ? ", [
|
||||||
null, groupID
|
null, groupID,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,15 @@ const { marked } = require("marked");
|
||||||
const { Feed } = require("feed");
|
const { Feed } = require("feed");
|
||||||
const config = require("../config");
|
const config = require("../config");
|
||||||
|
|
||||||
const { STATUS_PAGE_ALL_DOWN, STATUS_PAGE_ALL_UP, STATUS_PAGE_MAINTENANCE, STATUS_PAGE_PARTIAL_DOWN, UP, MAINTENANCE, DOWN } = require("../../src/util");
|
const {
|
||||||
|
STATUS_PAGE_ALL_DOWN,
|
||||||
|
STATUS_PAGE_ALL_UP,
|
||||||
|
STATUS_PAGE_MAINTENANCE,
|
||||||
|
STATUS_PAGE_PARTIAL_DOWN,
|
||||||
|
UP,
|
||||||
|
MAINTENANCE,
|
||||||
|
DOWN,
|
||||||
|
} = require("../../src/util");
|
||||||
|
|
||||||
class StatusPage extends BeanModel {
|
class StatusPage extends BeanModel {
|
||||||
|
|
||||||
|
@ -16,7 +24,7 @@ class StatusPage extends BeanModel {
|
||||||
* Like this: { "test-uptime.kuma.pet": "default" }
|
* Like this: { "test-uptime.kuma.pet": "default" }
|
||||||
* @type {{}}
|
* @type {{}}
|
||||||
*/
|
*/
|
||||||
static domainMappingList = { };
|
static domainMappingList = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle responses to RSS pages
|
* Handle responses to RSS pages
|
||||||
|
@ -26,7 +34,7 @@ class StatusPage extends BeanModel {
|
||||||
*/
|
*/
|
||||||
static async handleStatusPageRSSResponse(response, slug) {
|
static async handleStatusPageRSSResponse(response, slug) {
|
||||||
let statusPage = await R.findOne("status_page", " slug = ? ", [
|
let statusPage = await R.findOne("status_page", " slug = ? ", [
|
||||||
slug
|
slug,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (statusPage) {
|
if (statusPage) {
|
||||||
|
@ -51,7 +59,7 @@ class StatusPage extends BeanModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
let statusPage = await R.findOne("status_page", " slug = ? ", [
|
let statusPage = await R.findOne("status_page", " slug = ? ", [
|
||||||
slug
|
slug,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (statusPage) {
|
if (statusPage) {
|
||||||
|
@ -68,7 +76,10 @@ class StatusPage extends BeanModel {
|
||||||
* @returns {Promise<string>} the rendered html
|
* @returns {Promise<string>} the rendered html
|
||||||
*/
|
*/
|
||||||
static async renderRSS(statusPage, slug) {
|
static async renderRSS(statusPage, slug) {
|
||||||
const { heartbeats, statusDescription } = await StatusPage.getRSSPageData(statusPage);
|
const {
|
||||||
|
heartbeats,
|
||||||
|
statusDescription,
|
||||||
|
} = await StatusPage.getRSSPageData(statusPage);
|
||||||
|
|
||||||
let proto = config.isSSL ? "https" : "http";
|
let proto = config.isSSL ? "https" : "http";
|
||||||
let host = `${proto}://${config.hostname || "localhost"}:${config.port}/status/${slug}`;
|
let host = `${proto}://${config.hostname || "localhost"}:${config.port}/status/${slug}`;
|
||||||
|
@ -135,7 +146,7 @@ class StatusPage extends BeanModel {
|
||||||
// Preload data
|
// Preload data
|
||||||
// Add jsesc, fix https://github.com/louislam/uptime-kuma/issues/2186
|
// Add jsesc, fix https://github.com/louislam/uptime-kuma/issues/2186
|
||||||
const escapedJSONObject = jsesc(await StatusPage.getStatusPageData(statusPage), {
|
const escapedJSONObject = jsesc(await StatusPage.getStatusPageData(statusPage), {
|
||||||
"isScriptContext": true
|
"isScriptContext": true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const script = $(`
|
const script = $(`
|
||||||
|
@ -174,7 +185,7 @@ class StatusPage extends BeanModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! hasUp) {
|
if (!hasUp) {
|
||||||
status = STATUS_PAGE_ALL_DOWN;
|
status = STATUS_PAGE_ALL_DOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,7 +234,7 @@ class StatusPage extends BeanModel {
|
||||||
const showTags = !!statusPage.show_tags;
|
const showTags = !!statusPage.show_tags;
|
||||||
|
|
||||||
const list = await R.find("group", " public = 1 AND status_page_id = ? ORDER BY weight ", [
|
const list = await R.find("group", " public = 1 AND status_page_id = ? ORDER BY weight ", [
|
||||||
statusPage.id
|
statusPage.id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let heartbeats = [];
|
let heartbeats = [];
|
||||||
|
@ -236,7 +247,7 @@ class StatusPage extends BeanModel {
|
||||||
heartbeats.push({
|
heartbeats.push({
|
||||||
...monitor,
|
...monitor,
|
||||||
status: heartbeat.status,
|
status: heartbeat.status,
|
||||||
time: heartbeat.time
|
time: heartbeat.time,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -251,7 +262,7 @@ class StatusPage extends BeanModel {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
heartbeats,
|
heartbeats,
|
||||||
statusDescription
|
statusDescription,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,7 +290,7 @@ class StatusPage extends BeanModel {
|
||||||
const showTags = !!statusPage.show_tags;
|
const showTags = !!statusPage.show_tags;
|
||||||
|
|
||||||
const list = await R.find("group", " public = 1 AND status_page_id = ? ORDER BY weight ", [
|
const list = await R.find("group", " public = 1 AND status_page_id = ? ORDER BY weight ", [
|
||||||
statusPage.id
|
statusPage.id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
for (let groupBean of list) {
|
for (let groupBean of list) {
|
||||||
|
@ -442,7 +453,7 @@ class StatusPage extends BeanModel {
|
||||||
*/
|
*/
|
||||||
static async slugToID(slug) {
|
static async slugToID(slug) {
|
||||||
return await R.getCell("SELECT id FROM status_page WHERE slug = ? ", [
|
return await R.getCell("SELECT id FROM status_page WHERE slug = ? ", [
|
||||||
slug
|
slug,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,10 @@ const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||||
const passwordHash = require("../password-hash");
|
const passwordHash = require("../password-hash");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const jwt = require("jsonwebtoken");
|
const jwt = require("jsonwebtoken");
|
||||||
const { shake256, SHAKE256_LENGTH } = require("../util-server");
|
const {
|
||||||
|
shake256,
|
||||||
|
SHAKE256_LENGTH,
|
||||||
|
} = require("../util-server");
|
||||||
|
|
||||||
class User extends BeanModel {
|
class User extends BeanModel {
|
||||||
/**
|
/**
|
||||||
|
@ -15,7 +18,7 @@ class User extends BeanModel {
|
||||||
static async resetPassword(userID, newPassword) {
|
static async resetPassword(userID, newPassword) {
|
||||||
await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [
|
await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [
|
||||||
passwordHash.generate(newPassword),
|
passwordHash.generate(newPassword),
|
||||||
userID
|
userID,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +32,7 @@ class User extends BeanModel {
|
||||||
|
|
||||||
await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [
|
await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [
|
||||||
hashedPassword,
|
hashedPassword,
|
||||||
this.id
|
this.id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.password = hashedPassword;
|
this.password = hashedPassword;
|
||||||
|
|
|
@ -100,7 +100,7 @@ function ApiCache() {
|
||||||
* Generated by Trelent
|
* Generated by Trelent
|
||||||
*/
|
*/
|
||||||
function debug(a, b, c, d) {
|
function debug(a, b, c, d) {
|
||||||
let arr = ["\x1b[36m[apicache]\x1b[0m", a, b, c, d].filter(function (arg) {
|
let arr = [ "\x1b[36m[apicache]\x1b[0m", a, b, c, d ].filter(function (arg) {
|
||||||
return arg !== undefined;
|
return arg !== undefined;
|
||||||
});
|
});
|
||||||
let debugEnv = process.env.DEBUG && process.env.DEBUG.split(",").indexOf("apicache") !== -1;
|
let debugEnv = process.env.DEBUG && process.env.DEBUG.split(",").indexOf("apicache") !== -1;
|
||||||
|
@ -210,7 +210,8 @@ function ApiCache() {
|
||||||
try {
|
try {
|
||||||
redis.hset(key, "response", JSON.stringify(value));
|
redis.hset(key, "response", JSON.stringify(value));
|
||||||
redis.hset(key, "duration", duration);
|
redis.hset(key, "duration", duration);
|
||||||
redis.expire(key, duration / 1000, expireCallback || function () {});
|
redis.expire(key, duration / 1000, expireCallback || function () {
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
debug("[apicache] error in redis.hset()");
|
debug("[apicache] error in redis.hset()");
|
||||||
}
|
}
|
||||||
|
@ -247,8 +248,8 @@ function ApiCache() {
|
||||||
}
|
}
|
||||||
|
|
||||||
res._apicache.content = Buffer.concat(
|
res._apicache.content = Buffer.concat(
|
||||||
[oldContent, content],
|
[ oldContent, content ],
|
||||||
oldContent.length + content.length
|
oldContent.length + content.length,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
res._apicache.content = content;
|
res._apicache.content = content;
|
||||||
|
@ -268,7 +269,7 @@ function ApiCache() {
|
||||||
* @param {function(Object, Object):boolean} toggle
|
* @param {function(Object, Object):boolean} toggle
|
||||||
*/
|
*/
|
||||||
function makeResponseCacheable(req, res, next, key, duration, strDuration, 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 = {
|
res._apicache = {
|
||||||
write: res.write,
|
write: res.write,
|
||||||
writeHead: res.writeHead,
|
writeHead: res.writeHead,
|
||||||
|
@ -314,7 +315,7 @@ function ApiCache() {
|
||||||
res.statusCode,
|
res.statusCode,
|
||||||
headers,
|
headers,
|
||||||
res._apicache.content,
|
res._apicache.content,
|
||||||
encoding
|
encoding,
|
||||||
);
|
);
|
||||||
cacheResponse(key, cacheObject, duration);
|
cacheResponse(key, cacheObject, duration);
|
||||||
|
|
||||||
|
@ -367,7 +368,7 @@ function ApiCache() {
|
||||||
let data = cacheObject.data;
|
let data = cacheObject.data;
|
||||||
if (data && data.type === "Buffer") {
|
if (data && data.type === "Buffer") {
|
||||||
data =
|
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
|
// test Etag against If-None-Match for 304
|
||||||
|
@ -511,15 +512,15 @@ function ApiCache() {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return cache performance statistics (hit rate). Suitable for
|
* Return cache performance statistics (hit rate). Suitable for
|
||||||
* putting into a route:
|
* putting into a route:
|
||||||
* <code>
|
* <code>
|
||||||
* app.get('/api/cache/performance', (req, res) => {
|
* app.get('/api/cache/performance', (req, res) => {
|
||||||
* res.json(apicache.getPerformance())
|
* res.json(apicache.getPerformance())
|
||||||
* })
|
* })
|
||||||
* </code>
|
* </code>
|
||||||
* @returns {any[]}
|
* @returns {any[]}
|
||||||
*/
|
*/
|
||||||
this.getPerformance = function () {
|
this.getPerformance = function () {
|
||||||
return performanceArray.map(function (p) {
|
return performanceArray.map(function (p) {
|
||||||
return p.report();
|
return p.report();
|
||||||
|
@ -573,7 +574,8 @@ function ApiCache() {
|
||||||
* A Function for non tracking performance
|
* A Function for non tracking performance
|
||||||
*/
|
*/
|
||||||
function NOOPCachePerformance() {
|
function NOOPCachePerformance() {
|
||||||
this.report = this.hit = this.miss = function () {}; // noop;
|
this.report = this.hit = this.miss = function () {
|
||||||
|
}; // noop;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -762,8 +764,8 @@ function ApiCache() {
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
req.headers["x-apicache-bypass"] ||
|
req.headers["x-apicache-bypass"] ||
|
||||||
req.headers["x-apicache-force-fetch"] ||
|
req.headers["x-apicache-force-fetch"] ||
|
||||||
(opt.respectCacheControl && req.headers["cache-control"] == "no-cache")
|
(opt.respectCacheControl && req.headers["cache-control"] == "no-cache")
|
||||||
) {
|
) {
|
||||||
return bypass();
|
return bypass();
|
||||||
}
|
}
|
||||||
|
@ -826,7 +828,7 @@ function ApiCache() {
|
||||||
JSON.parse(obj.response),
|
JSON.parse(obj.response),
|
||||||
middlewareToggle,
|
middlewareToggle,
|
||||||
next,
|
next,
|
||||||
duration
|
duration,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
perf.miss(key);
|
perf.miss(key);
|
||||||
|
@ -837,7 +839,7 @@ function ApiCache() {
|
||||||
key,
|
key,
|
||||||
duration,
|
duration,
|
||||||
strDuration,
|
strDuration,
|
||||||
middlewareToggle
|
middlewareToggle,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@ const apicache = require("./apicache");
|
||||||
|
|
||||||
apicache.options({
|
apicache.options({
|
||||||
headerBlacklist: [
|
headerBlacklist: [
|
||||||
"cache-control"
|
"cache-control",
|
||||||
],
|
],
|
||||||
headers: {
|
headers: {
|
||||||
// Disable client side cache, only server side cache.
|
// Disable client side cache, only server side cache.
|
||||||
|
|
|
@ -22,7 +22,7 @@ MemoryCache.prototype.add = function (key, value, time, timeoutCallback) {
|
||||||
timeout: setTimeout(function () {
|
timeout: setTimeout(function () {
|
||||||
instance.delete(key);
|
instance.delete(key);
|
||||||
return timeoutCallback && typeof timeoutCallback === "function" && timeoutCallback(value, key);
|
return timeoutCallback && typeof timeoutCallback === "function" && timeoutCallback(value, key);
|
||||||
}, time)
|
}, time),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.cache[key] = entry;
|
this.cache[key] = entry;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
'use strict';
|
"use strict";
|
||||||
// Original file https://raw.githubusercontent.com/elasticio/node-ntlm-client/master/lib/flags.js
|
// Original file https://raw.githubusercontent.com/elasticio/node-ntlm-client/master/lib/flags.js
|
||||||
module.exports.NTLMFLAG_NEGOTIATE_UNICODE = 1 << 0;
|
module.exports.NTLMFLAG_NEGOTIATE_UNICODE = 1 << 0;
|
||||||
/* Indicates that Unicode strings are supported for use in security buffer
|
/* Indicates that Unicode strings are supported for use in security buffer
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
'use strict';
|
"use strict";
|
||||||
// Original source at https://github.com/elasticio/node-ntlm-client/blob/master/lib/hash.js
|
// Original source at https://github.com/elasticio/node-ntlm-client/blob/master/lib/hash.js
|
||||||
var crypto = require('crypto');
|
var crypto = require("crypto");
|
||||||
|
|
||||||
function createLMResponse(challenge, lmhash) {
|
function createLMResponse(challenge, lmhash) {
|
||||||
var buf = new Buffer.alloc(24), pwBuffer = new Buffer.alloc(21).fill(0);
|
var buf = new Buffer.alloc(24), pwBuffer = new Buffer.alloc(21).fill(0);
|
||||||
lmhash.copy(pwBuffer);
|
lmhash.copy(pwBuffer);
|
||||||
|
@ -9,19 +10,21 @@ function createLMResponse(challenge, lmhash) {
|
||||||
calculateDES(pwBuffer.slice(14), challenge).copy(buf, 16);
|
calculateDES(pwBuffer.slice(14), challenge).copy(buf, 16);
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createLMHash(password) {
|
function createLMHash(password) {
|
||||||
var buf = new Buffer.alloc(16), pwBuffer = new Buffer.alloc(14), magicKey = new Buffer.from('KGS!@#$%', 'ascii');
|
var buf = new Buffer.alloc(16), pwBuffer = new Buffer.alloc(14), magicKey = new Buffer.from("KGS!@#$%", "ascii");
|
||||||
if (password.length > 14) {
|
if (password.length > 14) {
|
||||||
buf.fill(0);
|
buf.fill(0);
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
pwBuffer.fill(0);
|
pwBuffer.fill(0);
|
||||||
pwBuffer.write(password.toUpperCase(), 0, 'ascii');
|
pwBuffer.write(password.toUpperCase(), 0, "ascii");
|
||||||
return Buffer.concat([
|
return Buffer.concat([
|
||||||
calculateDES(pwBuffer.slice(0, 7), magicKey),
|
calculateDES(pwBuffer.slice(0, 7), magicKey),
|
||||||
calculateDES(pwBuffer.slice(7), magicKey)
|
calculateDES(pwBuffer.slice(7), magicKey),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateDES(key, message) {
|
function calculateDES(key, message) {
|
||||||
var desKey = new Buffer.alloc(8);
|
var desKey = new Buffer.alloc(8);
|
||||||
desKey[0] = key[0] & 0xFE;
|
desKey[0] = key[0] & 0xFE;
|
||||||
|
@ -39,9 +42,10 @@ function calculateDES(key, message) {
|
||||||
}
|
}
|
||||||
desKey[i] |= (parity % 2) === 0 ? 1 : 0;
|
desKey[i] |= (parity % 2) === 0 ? 1 : 0;
|
||||||
}
|
}
|
||||||
var des = crypto.createCipheriv('DES-ECB', desKey, '');
|
var des = crypto.createCipheriv("DES-ECB", desKey, "");
|
||||||
return des.update(message);
|
return des.update(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNTLMResponse(challenge, ntlmhash) {
|
function createNTLMResponse(challenge, ntlmhash) {
|
||||||
var buf = new Buffer.alloc(24), ntlmBuffer = new Buffer.alloc(21).fill(0);
|
var buf = new Buffer.alloc(24), ntlmBuffer = new Buffer.alloc(21).fill(0);
|
||||||
ntlmhash.copy(ntlmBuffer);
|
ntlmhash.copy(ntlmBuffer);
|
||||||
|
@ -50,30 +54,36 @@ function createNTLMResponse(challenge, ntlmhash) {
|
||||||
calculateDES(ntlmBuffer.slice(14), challenge).copy(buf, 16);
|
calculateDES(ntlmBuffer.slice(14), challenge).copy(buf, 16);
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNTLMHash(password) {
|
function createNTLMHash(password) {
|
||||||
var md4sum = crypto.createHash('md4');
|
var md4sum = crypto.createHash("md4");
|
||||||
md4sum.update(new Buffer.from(password, 'ucs2'));
|
md4sum.update(new Buffer.from(password, "ucs2"));
|
||||||
return md4sum.digest();
|
return md4sum.digest();
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNTLMv2Hash(ntlmhash, username, authTargetName) {
|
function createNTLMv2Hash(ntlmhash, username, authTargetName) {
|
||||||
var hmac = crypto.createHmac('md5', ntlmhash);
|
var hmac = crypto.createHmac("md5", ntlmhash);
|
||||||
hmac.update(new Buffer.from(username.toUpperCase() + authTargetName, 'ucs2'));
|
hmac.update(new Buffer.from(username.toUpperCase() + authTargetName, "ucs2"));
|
||||||
return hmac.digest();
|
return hmac.digest();
|
||||||
}
|
}
|
||||||
|
|
||||||
function createLMv2Response(type2message, username, ntlmhash, nonce, targetName) {
|
function createLMv2Response(type2message, username, ntlmhash, nonce, targetName) {
|
||||||
var buf = new Buffer.alloc(24), ntlm2hash = createNTLMv2Hash(ntlmhash, username, targetName), hmac = crypto.createHmac('md5', ntlm2hash);
|
var buf = new Buffer.alloc(24), ntlm2hash = createNTLMv2Hash(ntlmhash, username, targetName),
|
||||||
|
hmac = crypto.createHmac("md5", ntlm2hash);
|
||||||
//server challenge
|
//server challenge
|
||||||
type2message.challenge.copy(buf, 8);
|
type2message.challenge.copy(buf, 8);
|
||||||
//client nonce
|
//client nonce
|
||||||
buf.write(nonce || createPseudoRandomValue(16), 16, 'hex');
|
buf.write(nonce || createPseudoRandomValue(16), 16, "hex");
|
||||||
//create hash
|
//create hash
|
||||||
hmac.update(buf.slice(8));
|
hmac.update(buf.slice(8));
|
||||||
var hashedBuffer = hmac.digest();
|
var hashedBuffer = hmac.digest();
|
||||||
hashedBuffer.copy(buf);
|
hashedBuffer.copy(buf);
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNTLMv2Response(type2message, username, ntlmhash, nonce, targetName) {
|
function createNTLMv2Response(type2message, username, ntlmhash, nonce, targetName) {
|
||||||
var buf = new Buffer.alloc(48 + type2message.targetInfo.buffer.length), ntlm2hash = createNTLMv2Hash(ntlmhash, username, targetName), hmac = crypto.createHmac('md5', ntlm2hash);
|
var buf = new Buffer.alloc(48 + type2message.targetInfo.buffer.length),
|
||||||
|
ntlm2hash = createNTLMv2Hash(ntlmhash, username, targetName), hmac = crypto.createHmac("md5", ntlm2hash);
|
||||||
//the first 8 bytes are spare to store the hashed value before the blob
|
//the first 8 bytes are spare to store the hashed value before the blob
|
||||||
//server challenge
|
//server challenge
|
||||||
type2message.challenge.copy(buf, 8);
|
type2message.challenge.copy(buf, 8);
|
||||||
|
@ -86,12 +96,12 @@ function createNTLMv2Response(type2message, username, ntlmhash, nonce, targetNam
|
||||||
// maybe think about a different solution here
|
// maybe think about a different solution here
|
||||||
// 11644473600000 = diff between 1970 and 1601
|
// 11644473600000 = diff between 1970 and 1601
|
||||||
var timestamp = ((Date.now() + 11644473600000) * 10000).toString(16);
|
var timestamp = ((Date.now() + 11644473600000) * 10000).toString(16);
|
||||||
var timestampLow = Number('0x' + timestamp.substring(Math.max(0, timestamp.length - 8)));
|
var timestampLow = Number("0x" + timestamp.substring(Math.max(0, timestamp.length - 8)));
|
||||||
var timestampHigh = Number('0x' + timestamp.substring(0, Math.max(0, timestamp.length - 8)));
|
var timestampHigh = Number("0x" + timestamp.substring(0, Math.max(0, timestamp.length - 8)));
|
||||||
buf.writeUInt32LE(timestampLow, 24, false);
|
buf.writeUInt32LE(timestampLow, 24, false);
|
||||||
buf.writeUInt32LE(timestampHigh, 28, false);
|
buf.writeUInt32LE(timestampHigh, 28, false);
|
||||||
//random client nonce
|
//random client nonce
|
||||||
buf.write(nonce || createPseudoRandomValue(16), 32, 'hex');
|
buf.write(nonce || createPseudoRandomValue(16), 32, "hex");
|
||||||
//zero
|
//zero
|
||||||
buf.writeUInt32LE(0, 40);
|
buf.writeUInt32LE(0, 40);
|
||||||
//complete target information block from type 2 message
|
//complete target information block from type 2 message
|
||||||
|
@ -103,13 +113,15 @@ function createNTLMv2Response(type2message, username, ntlmhash, nonce, targetNam
|
||||||
hashedBuffer.copy(buf);
|
hashedBuffer.copy(buf);
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPseudoRandomValue(length) {
|
function createPseudoRandomValue(length) {
|
||||||
var str = '';
|
var str = "";
|
||||||
while (str.length < length) {
|
while (str.length < length) {
|
||||||
str += Math.floor(Math.random() * 16).toString(16);
|
str += Math.floor(Math.random() * 16).toString(16);
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createLMHash: createLMHash,
|
createLMHash: createLMHash,
|
||||||
createNTLMHash: createNTLMHash,
|
createNTLMHash: createNTLMHash,
|
||||||
|
@ -117,6 +129,6 @@ module.exports = {
|
||||||
createNTLMResponse: createNTLMResponse,
|
createNTLMResponse: createNTLMResponse,
|
||||||
createLMv2Response: createLMv2Response,
|
createLMv2Response: createLMv2Response,
|
||||||
createNTLMv2Response: createNTLMv2Response,
|
createNTLMv2Response: createNTLMv2Response,
|
||||||
createPseudoRandomValue: createPseudoRandomValue
|
createPseudoRandomValue: createPseudoRandomValue,
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=hash.js.map
|
//# sourceMappingURL=hash.js.map
|
|
@ -1,13 +1,14 @@
|
||||||
'use strict';
|
"use strict";
|
||||||
// Original file https://raw.githubusercontent.com/elasticio/node-ntlm-client/master/lib/ntlm.js
|
// Original file https://raw.githubusercontent.com/elasticio/node-ntlm-client/master/lib/ntlm.js
|
||||||
var os = require('os'), flags = require('./flags'), hash = require('./hash');
|
var os = require("os"), flags = require("./flags"), hash = require("./hash");
|
||||||
var NTLMSIGNATURE = "NTLMSSP\0";
|
var NTLMSIGNATURE = "NTLMSSP\0";
|
||||||
|
|
||||||
function createType1Message(workstation, target) {
|
function createType1Message(workstation, target) {
|
||||||
var dataPos = 32, pos = 0, buf = new Buffer.alloc(1024);
|
var dataPos = 32, pos = 0, buf = new Buffer.alloc(1024);
|
||||||
workstation = workstation === undefined ? os.hostname() : workstation;
|
workstation = workstation === undefined ? os.hostname() : workstation;
|
||||||
target = target === undefined ? '' : target;
|
target = target === undefined ? "" : target;
|
||||||
//signature
|
//signature
|
||||||
buf.write(NTLMSIGNATURE, pos, NTLMSIGNATURE.length, 'ascii');
|
buf.write(NTLMSIGNATURE, pos, NTLMSIGNATURE.length, "ascii");
|
||||||
pos += NTLMSIGNATURE.length;
|
pos += NTLMSIGNATURE.length;
|
||||||
//message type
|
//message type
|
||||||
buf.writeUInt32LE(1, pos);
|
buf.writeUInt32LE(1, pos);
|
||||||
|
@ -27,7 +28,7 @@ function createType1Message(workstation, target) {
|
||||||
buf.writeUInt32LE(target.length === 0 ? 0 : dataPos, pos);
|
buf.writeUInt32LE(target.length === 0 ? 0 : dataPos, pos);
|
||||||
pos += 4;
|
pos += 4;
|
||||||
if (target.length > 0) {
|
if (target.length > 0) {
|
||||||
dataPos += buf.write(target, dataPos, 'ascii');
|
dataPos += buf.write(target, dataPos, "ascii");
|
||||||
}
|
}
|
||||||
//workstation security buffer
|
//workstation security buffer
|
||||||
buf.writeUInt16LE(workstation.length, pos);
|
buf.writeUInt16LE(workstation.length, pos);
|
||||||
|
@ -37,39 +38,39 @@ function createType1Message(workstation, target) {
|
||||||
buf.writeUInt32LE(workstation.length === 0 ? 0 : dataPos, pos);
|
buf.writeUInt32LE(workstation.length === 0 ? 0 : dataPos, pos);
|
||||||
pos += 4;
|
pos += 4;
|
||||||
if (workstation.length > 0) {
|
if (workstation.length > 0) {
|
||||||
dataPos += buf.write(workstation, dataPos, 'ascii');
|
dataPos += buf.write(workstation, dataPos, "ascii");
|
||||||
}
|
}
|
||||||
return 'NTLM ' + buf.toString('base64', 0, dataPos);
|
return "NTLM " + buf.toString("base64", 0, dataPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
function decodeType2Message(str) {
|
function decodeType2Message(str) {
|
||||||
if (str === undefined) {
|
if (str === undefined) {
|
||||||
throw new Error('Invalid argument');
|
throw new Error("Invalid argument");
|
||||||
}
|
}
|
||||||
//convenience
|
//convenience
|
||||||
if (Object.prototype.toString.call(str) !== '[object String]') {
|
if (Object.prototype.toString.call(str) !== "[object String]") {
|
||||||
if (str.hasOwnProperty('headers') && str.headers.hasOwnProperty('www-authenticate')) {
|
if (str.hasOwnProperty("headers") && str.headers.hasOwnProperty("www-authenticate")) {
|
||||||
str = str.headers['www-authenticate'];
|
str = str.headers["www-authenticate"];
|
||||||
}
|
} else {
|
||||||
else {
|
throw new Error("Invalid argument");
|
||||||
throw new Error('Invalid argument');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var ntlmMatch = /^NTLM ([^,\s]+)/.exec(str);
|
var ntlmMatch = /^NTLM ([^,\s]+)/.exec(str);
|
||||||
if (ntlmMatch) {
|
if (ntlmMatch) {
|
||||||
str = ntlmMatch[1];
|
str = ntlmMatch[1];
|
||||||
}
|
}
|
||||||
var buf = new Buffer.from(str, 'base64'), obj = {};
|
var buf = new Buffer.from(str, "base64"), obj = {};
|
||||||
//check signature
|
//check signature
|
||||||
if (buf.toString('ascii', 0, NTLMSIGNATURE.length) !== NTLMSIGNATURE) {
|
if (buf.toString("ascii", 0, NTLMSIGNATURE.length) !== NTLMSIGNATURE) {
|
||||||
throw new Error('Invalid message signature: ' + str);
|
throw new Error("Invalid message signature: " + str);
|
||||||
}
|
}
|
||||||
//check message type
|
//check message type
|
||||||
if (buf.readUInt32LE(NTLMSIGNATURE.length) !== 2) {
|
if (buf.readUInt32LE(NTLMSIGNATURE.length) !== 2) {
|
||||||
throw new Error('Invalid message type (no type 2)');
|
throw new Error("Invalid message type (no type 2)");
|
||||||
}
|
}
|
||||||
//read flags
|
//read flags
|
||||||
obj.flags = buf.readUInt32LE(20);
|
obj.flags = buf.readUInt32LE(20);
|
||||||
obj.encoding = (obj.flags & flags.NTLMFLAG_NEGOTIATE_OEM) ? 'ascii' : 'ucs2';
|
obj.encoding = (obj.flags & flags.NTLMFLAG_NEGOTIATE_OEM) ? "ascii" : "ucs2";
|
||||||
obj.version = (obj.flags & flags.NTLMFLAG_NEGOTIATE_NTLM2_KEY) ? 2 : 1;
|
obj.version = (obj.flags & flags.NTLMFLAG_NEGOTIATE_NTLM2_KEY) ? 2 : 1;
|
||||||
obj.challenge = buf.slice(24, 32);
|
obj.challenge = buf.slice(24, 32);
|
||||||
//read target name
|
//read target name
|
||||||
|
@ -78,10 +79,10 @@ function decodeType2Message(str) {
|
||||||
//skipping allocated space
|
//skipping allocated space
|
||||||
var offset = buf.readUInt32LE(16);
|
var offset = buf.readUInt32LE(16);
|
||||||
if (length === 0) {
|
if (length === 0) {
|
||||||
return '';
|
return "";
|
||||||
}
|
}
|
||||||
if ((offset + length) > buf.length || offset < 32) {
|
if ((offset + length) > buf.length || offset < 32) {
|
||||||
throw new Error('Bad type 2 message');
|
throw new Error("Bad type 2 message");
|
||||||
}
|
}
|
||||||
return buf.toString(obj.encoding, offset, offset + length);
|
return buf.toString(obj.encoding, offset, offset + length);
|
||||||
})();
|
})();
|
||||||
|
@ -98,7 +99,7 @@ function decodeType2Message(str) {
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
if ((offset + length) > buf.length || offset < 32) {
|
if ((offset + length) > buf.length || offset < 32) {
|
||||||
throw new Error('Bad type 2 message');
|
throw new Error("Bad type 2 message");
|
||||||
}
|
}
|
||||||
var pos = offset;
|
var pos = offset;
|
||||||
while (pos < (offset + length)) {
|
while (pos < (offset + length)) {
|
||||||
|
@ -113,37 +114,38 @@ function decodeType2Message(str) {
|
||||||
var blockTypeStr = void 0;
|
var blockTypeStr = void 0;
|
||||||
switch (blockType) {
|
switch (blockType) {
|
||||||
case 1:
|
case 1:
|
||||||
blockTypeStr = 'SERVER';
|
blockTypeStr = "SERVER";
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
blockTypeStr = 'DOMAIN';
|
blockTypeStr = "DOMAIN";
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
blockTypeStr = 'FQDN';
|
blockTypeStr = "FQDN";
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
blockTypeStr = 'DNS';
|
blockTypeStr = "DNS";
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
blockTypeStr = 'PARENT_DNS';
|
blockTypeStr = "PARENT_DNS";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
blockTypeStr = '';
|
blockTypeStr = "";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (blockTypeStr) {
|
if (blockTypeStr) {
|
||||||
info[blockTypeStr] = buf.toString('ucs2', pos, pos + blockLength);
|
info[blockTypeStr] = buf.toString("ucs2", pos, pos + blockLength);
|
||||||
}
|
}
|
||||||
pos += blockLength;
|
pos += blockLength;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
parsed: info,
|
parsed: info,
|
||||||
buffer: targetInfoBuffer
|
buffer: targetInfoBuffer,
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createType3Message(type2Message, username, password, workstation, target) {
|
function createType3Message(type2Message, username, password, workstation, target) {
|
||||||
var dataPos = 52, buf = new Buffer.alloc(1024);
|
var dataPos = 52, buf = new Buffer.alloc(1024);
|
||||||
if (workstation === undefined) {
|
if (workstation === undefined) {
|
||||||
|
@ -153,12 +155,14 @@ function createType3Message(type2Message, username, password, workstation, targe
|
||||||
target = type2Message.targetName;
|
target = type2Message.targetName;
|
||||||
}
|
}
|
||||||
//signature
|
//signature
|
||||||
buf.write(NTLMSIGNATURE, 0, NTLMSIGNATURE.length, 'ascii');
|
buf.write(NTLMSIGNATURE, 0, NTLMSIGNATURE.length, "ascii");
|
||||||
//message type
|
//message type
|
||||||
buf.writeUInt32LE(3, 8);
|
buf.writeUInt32LE(3, 8);
|
||||||
if (type2Message.version === 2) {
|
if (type2Message.version === 2) {
|
||||||
dataPos = 64;
|
dataPos = 64;
|
||||||
var ntlmHash = hash.createNTLMHash(password), nonce = hash.createPseudoRandomValue(16), lmv2 = hash.createLMv2Response(type2Message, username, ntlmHash, nonce, target), ntlmv2 = hash.createNTLMv2Response(type2Message, username, ntlmHash, nonce, target);
|
var ntlmHash = hash.createNTLMHash(password), nonce = hash.createPseudoRandomValue(16),
|
||||||
|
lmv2 = hash.createLMv2Response(type2Message, username, ntlmHash, nonce, target),
|
||||||
|
ntlmv2 = hash.createNTLMv2Response(type2Message, username, ntlmHash, nonce, target);
|
||||||
//lmv2 security buffer
|
//lmv2 security buffer
|
||||||
buf.writeUInt16LE(lmv2.length, 12);
|
buf.writeUInt16LE(lmv2.length, 12);
|
||||||
buf.writeUInt16LE(lmv2.length, 14);
|
buf.writeUInt16LE(lmv2.length, 14);
|
||||||
|
@ -171,9 +175,10 @@ function createType3Message(type2Message, username, password, workstation, targe
|
||||||
buf.writeUInt32LE(dataPos, 24);
|
buf.writeUInt32LE(dataPos, 24);
|
||||||
ntlmv2.copy(buf, dataPos);
|
ntlmv2.copy(buf, dataPos);
|
||||||
dataPos += ntlmv2.length;
|
dataPos += ntlmv2.length;
|
||||||
}
|
} else {
|
||||||
else {
|
var lmHash = hash.createLMHash(password), ntlmHash = hash.createNTLMHash(password),
|
||||||
var lmHash = hash.createLMHash(password), ntlmHash = hash.createNTLMHash(password), lm = hash.createLMResponse(type2Message.challenge, lmHash), ntlm = hash.createNTLMResponse(type2Message.challenge, ntlmHash);
|
lm = hash.createLMResponse(type2Message.challenge, lmHash),
|
||||||
|
ntlm = hash.createNTLMResponse(type2Message.challenge, ntlmHash);
|
||||||
//lm security buffer
|
//lm security buffer
|
||||||
buf.writeUInt16LE(lm.length, 12);
|
buf.writeUInt16LE(lm.length, 12);
|
||||||
buf.writeUInt16LE(lm.length, 14);
|
buf.writeUInt16LE(lm.length, 14);
|
||||||
|
@ -188,18 +193,18 @@ function createType3Message(type2Message, username, password, workstation, targe
|
||||||
dataPos += ntlm.length;
|
dataPos += ntlm.length;
|
||||||
}
|
}
|
||||||
//target name security buffer
|
//target name security buffer
|
||||||
buf.writeUInt16LE(type2Message.encoding === 'ascii' ? target.length : target.length * 2, 28);
|
buf.writeUInt16LE(type2Message.encoding === "ascii" ? target.length : target.length * 2, 28);
|
||||||
buf.writeUInt16LE(type2Message.encoding === 'ascii' ? target.length : target.length * 2, 30);
|
buf.writeUInt16LE(type2Message.encoding === "ascii" ? target.length : target.length * 2, 30);
|
||||||
buf.writeUInt32LE(dataPos, 32);
|
buf.writeUInt32LE(dataPos, 32);
|
||||||
dataPos += buf.write(target, dataPos, type2Message.encoding);
|
dataPos += buf.write(target, dataPos, type2Message.encoding);
|
||||||
//user name security buffer
|
//user name security buffer
|
||||||
buf.writeUInt16LE(type2Message.encoding === 'ascii' ? username.length : username.length * 2, 36);
|
buf.writeUInt16LE(type2Message.encoding === "ascii" ? username.length : username.length * 2, 36);
|
||||||
buf.writeUInt16LE(type2Message.encoding === 'ascii' ? username.length : username.length * 2, 38);
|
buf.writeUInt16LE(type2Message.encoding === "ascii" ? username.length : username.length * 2, 38);
|
||||||
buf.writeUInt32LE(dataPos, 40);
|
buf.writeUInt32LE(dataPos, 40);
|
||||||
dataPos += buf.write(username, dataPos, type2Message.encoding);
|
dataPos += buf.write(username, dataPos, type2Message.encoding);
|
||||||
//workstation name security buffer
|
//workstation name security buffer
|
||||||
buf.writeUInt16LE(type2Message.encoding === 'ascii' ? workstation.length : workstation.length * 2, 44);
|
buf.writeUInt16LE(type2Message.encoding === "ascii" ? workstation.length : workstation.length * 2, 44);
|
||||||
buf.writeUInt16LE(type2Message.encoding === 'ascii' ? workstation.length : workstation.length * 2, 46);
|
buf.writeUInt16LE(type2Message.encoding === "ascii" ? workstation.length : workstation.length * 2, 46);
|
||||||
buf.writeUInt32LE(dataPos, 48);
|
buf.writeUInt32LE(dataPos, 48);
|
||||||
dataPos += buf.write(workstation, dataPos, type2Message.encoding);
|
dataPos += buf.write(workstation, dataPos, type2Message.encoding);
|
||||||
if (type2Message.version === 2) {
|
if (type2Message.version === 2) {
|
||||||
|
@ -210,11 +215,12 @@ function createType3Message(type2Message, username, password, workstation, targe
|
||||||
//flags
|
//flags
|
||||||
buf.writeUInt32LE(type2Message.flags, 60);
|
buf.writeUInt32LE(type2Message.flags, 60);
|
||||||
}
|
}
|
||||||
return 'NTLM ' + buf.toString('base64', 0, dataPos);
|
return "NTLM " + buf.toString("base64", 0, dataPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createType1Message: createType1Message,
|
createType1Message: createType1Message,
|
||||||
decodeType2Message: decodeType2Message,
|
decodeType2Message: decodeType2Message,
|
||||||
createType3Message: createType3Message
|
createType3Message: createType3Message,
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=ntlm.js.map
|
//# sourceMappingURL=ntlm.js.map
|
|
@ -1,57 +1,172 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function (o, m, k, k2) {
|
||||||
if (k2 === undefined) k2 = k;
|
if (k2 === undefined) {
|
||||||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
k2 = k;
|
||||||
}) : (function(o, m, k, k2) {
|
}
|
||||||
if (k2 === undefined) k2 = k;
|
Object.defineProperty(o, k2, {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return m[k];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}) : (function (o, m, k, k2) {
|
||||||
|
if (k2 === undefined) {
|
||||||
|
k2 = k;
|
||||||
|
}
|
||||||
o[k2] = m[k];
|
o[k2] = m[k];
|
||||||
}));
|
}));
|
||||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function (o, v) {
|
||||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
Object.defineProperty(o, "default", {
|
||||||
}) : function(o, v) {
|
enumerable: true,
|
||||||
|
value: v,
|
||||||
|
});
|
||||||
|
}) : function (o, v) {
|
||||||
o["default"] = v;
|
o["default"] = v;
|
||||||
});
|
});
|
||||||
var __importStar = (this && this.__importStar) || function (mod) {
|
var __importStar = (this && this.__importStar) || function (mod) {
|
||||||
if (mod && mod.__esModule) return mod;
|
if (mod && mod.__esModule) {
|
||||||
|
return mod;
|
||||||
|
}
|
||||||
var result = {};
|
var result = {};
|
||||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
if (mod != null) {
|
||||||
|
for (var k in mod) {
|
||||||
|
if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) {
|
||||||
|
__createBinding(result, mod, k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
__setModuleDefault(result, mod);
|
__setModuleDefault(result, mod);
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
function adopt(value) {
|
||||||
|
return value instanceof P ? value : new P(function (resolve) {
|
||||||
|
resolve(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return new (P || (P = Promise))(function (resolve, reject) {
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
function fulfilled(value) {
|
||||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
try {
|
||||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
step(generator.next(value));
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function rejected(value) {
|
||||||
|
try {
|
||||||
|
step(generator["throw"](value));
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function step(result) {
|
||||||
|
result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
|
||||||
|
}
|
||||||
|
|
||||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
var __generator = (this && this.__generator) || function (thisArg, body) {
|
var __generator = (this && this.__generator) || function (thisArg, body) {
|
||||||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
var _ = {
|
||||||
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
label: 0,
|
||||||
function verb(n) { return function (v) { return step([n, v]); }; }
|
sent: function () {
|
||||||
function step(op) {
|
if (t[0] & 1) {
|
||||||
if (f) throw new TypeError("Generator is already executing.");
|
throw t[1];
|
||||||
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, _);
|
return t[1];
|
||||||
} 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 };
|
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) {
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
|
@ -64,12 +179,13 @@ var ntlm = __importStar(require("./ntlm"));
|
||||||
var https = __importStar(require("https"));
|
var https = __importStar(require("https"));
|
||||||
var http = __importStar(require("http"));
|
var http = __importStar(require("http"));
|
||||||
var dev_null_1 = __importDefault(require("dev-null"));
|
var dev_null_1 = __importDefault(require("dev-null"));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param credentials An NtlmCredentials object containing the username and password
|
* @param credentials An NtlmCredentials object containing the username and password
|
||||||
* @param AxiosConfig The Axios config for the instance you wish to create
|
* @param AxiosConfig The Axios config for the instance you wish to create
|
||||||
*
|
*
|
||||||
* @returns This function returns an axios instance configured to use the provided credentials
|
* @returns This function returns an axios instance configured to use the provided credentials
|
||||||
*/
|
*/
|
||||||
function NtlmClient(credentials, AxiosConfig) {
|
function NtlmClient(credentials, AxiosConfig) {
|
||||||
var _this = this;
|
var _this = this;
|
||||||
var config = AxiosConfig !== null && AxiosConfig !== void 0 ? AxiosConfig : {};
|
var config = AxiosConfig !== null && AxiosConfig !== void 0 ? AxiosConfig : {};
|
||||||
|
@ -82,46 +198,56 @@ function NtlmClient(credentials, AxiosConfig) {
|
||||||
var client = axios_1.default.create(config);
|
var client = axios_1.default.create(config);
|
||||||
client.interceptors.response.use(function (response) {
|
client.interceptors.response.use(function (response) {
|
||||||
return response;
|
return response;
|
||||||
}, function (err) { return __awaiter(_this, void 0, void 0, function () {
|
}, function (err) {
|
||||||
var error, t1Msg, t2Msg, t3Msg, stream_1;
|
return __awaiter(_this, void 0, void 0, function () {
|
||||||
var _a;
|
var error, t1Msg, t2Msg, t3Msg, stream_1;
|
||||||
return __generator(this, function (_b) {
|
var _a;
|
||||||
switch (_b.label) {
|
return __generator(this, function (_b) {
|
||||||
case 0:
|
switch (_b.label) {
|
||||||
error = err.response;
|
case 0:
|
||||||
if (!(error && error.status === 401
|
error = err.response;
|
||||||
&& error.headers['www-authenticate']
|
if (!(error && error.status === 401
|
||||||
&& error.headers['www-authenticate'].includes('NTLM'))) return [3 /*break*/, 3];
|
&& error.headers["www-authenticate"]
|
||||||
// This length check is a hack because SharePoint is awkward and will
|
&& error.headers["www-authenticate"].includes("NTLM"))) {
|
||||||
// include the Negotiate option when responding with the T2 message
|
return [ 3 /*break*/, 3 ];
|
||||||
// There is nore we could do to ensure we are processing correctly,
|
}
|
||||||
// but this is the easiest option for now
|
// This length check is a hack because SharePoint is awkward and will
|
||||||
if (error.headers['www-authenticate'].length < 50) {
|
// include the Negotiate option when responding with the T2 message
|
||||||
t1Msg = ntlm.createType1Message(credentials.workstation, credentials.domain);
|
// There is nore we could do to ensure we are processing correctly,
|
||||||
error.config.headers["Authorization"] = t1Msg;
|
// but this is the easiest option for now
|
||||||
}
|
if (error.headers["www-authenticate"].length < 50) {
|
||||||
else {
|
t1Msg = ntlm.createType1Message(credentials.workstation, credentials.domain);
|
||||||
t2Msg = ntlm.decodeType2Message((error.headers['www-authenticate'].match(/^NTLM\s+(.+?)(,|\s+|$)/) || [])[1]);
|
error.config.headers["Authorization"] = t1Msg;
|
||||||
t3Msg = ntlm.createType3Message(t2Msg, credentials.username, credentials.password, credentials.workstation, credentials.domain);
|
} else {
|
||||||
error.config.headers["X-retry"] = "false";
|
t2Msg = ntlm.decodeType2Message((error.headers["www-authenticate"].match(/^NTLM\s+(.+?)(,|\s+|$)/) || [])[1]);
|
||||||
error.config.headers["Authorization"] = t3Msg;
|
t3Msg = ntlm.createType3Message(t2Msg, credentials.username, credentials.password, credentials.workstation, credentials.domain);
|
||||||
}
|
error.config.headers["X-retry"] = "false";
|
||||||
if (!(error.config.responseType === "stream")) return [3 /*break*/, 2];
|
error.config.headers["Authorization"] = t3Msg;
|
||||||
stream_1 = (_a = err.response) === null || _a === void 0 ? void 0 : _a.data;
|
}
|
||||||
if (!(stream_1 && !stream_1.readableEnded)) return [3 /*break*/, 2];
|
if (!(error.config.responseType === "stream")) {
|
||||||
return [4 /*yield*/, new Promise(function (resolve) {
|
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.pipe((0, dev_null_1.default)());
|
||||||
stream_1.once('close', resolve);
|
stream_1.once('close', resolve);
|
||||||
})];
|
}) ];
|
||||||
case 1:
|
case 1:
|
||||||
_b.sent();
|
_b.sent();
|
||||||
_b.label = 2;
|
_b.label = 2;
|
||||||
case 2: return [2 /*return*/, client(error.config)];
|
case 2:
|
||||||
case 3: throw err;
|
return [ 2 /*return*/, client(error.config) ];
|
||||||
}
|
case 3:
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}); });
|
});
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.NtlmClient = NtlmClient;
|
exports.NtlmClient = NtlmClient;
|
||||||
//# sourceMappingURL=ntlmClient.js.map
|
//# sourceMappingURL=ntlmClient.js.map
|
32
server/modules/dayjs/plugin/timezone.d.ts
vendored
32
server/modules/dayjs/plugin/timezone.d.ts
vendored
|
@ -1,20 +1,24 @@
|
||||||
import { PluginFunc, ConfigType } from 'dayjs'
|
import { PluginFunc, ConfigType } from "dayjs";
|
||||||
|
|
||||||
declare const plugin: PluginFunc
|
declare const plugin: PluginFunc;
|
||||||
export = plugin
|
export = plugin
|
||||||
|
|
||||||
declare module 'dayjs' {
|
declare module "dayjs" {
|
||||||
interface Dayjs {
|
interface Dayjs {
|
||||||
tz(timezone?: string, keepLocalTime?: boolean): Dayjs
|
tz(timezone?: string, keepLocalTime?: boolean): Dayjs;
|
||||||
offsetName(type?: 'short' | 'long'): string | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DayjsTimezone {
|
offsetName(type?: "short" | "long"): string | undefined;
|
||||||
(date: ConfigType, timezone?: string): Dayjs
|
}
|
||||||
(date: ConfigType, format: string, timezone?: string): Dayjs
|
|
||||||
guess(): string
|
|
||||||
setDefault(timezone?: string): void
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
day: 2,
|
day: 2,
|
||||||
hour: 3,
|
hour: 3,
|
||||||
minute: 4,
|
minute: 4,
|
||||||
second: 5
|
second: 5,
|
||||||
};
|
};
|
||||||
let e = {};
|
let e = {};
|
||||||
return function (n, i, o) {
|
return function (n, i, o) {
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
hour: "2-digit",
|
hour: "2-digit",
|
||||||
minute: "2-digit",
|
minute: "2-digit",
|
||||||
second: "2-digit",
|
second: "2-digit",
|
||||||
timeZoneName: i
|
timeZoneName: i,
|
||||||
}), e[o] = r), r;
|
}), e[o] = r), r;
|
||||||
}(n, i);
|
}(n, i);
|
||||||
return r.formatToParts(o);
|
return r.formatToParts(o);
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
const { MonitorType } = require("./monitor-type");
|
const { MonitorType } = require("./monitor-type");
|
||||||
const { UP, DOWN } = require("../../src/util");
|
const {
|
||||||
|
UP,
|
||||||
|
DOWN,
|
||||||
|
} = require("../../src/util");
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
const { dnsResolve } = require("../util-server");
|
const { dnsResolve } = require("../util-server");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
|
@ -14,7 +17,7 @@ class DnsMonitorType extends MonitorType {
|
||||||
supportsConditions = true;
|
supportsConditions = true;
|
||||||
|
|
||||||
conditionVariables = [
|
conditionVariables = [
|
||||||
new ConditionVariable("record", defaultStringOperators ),
|
new ConditionVariable("record", defaultStringOperators),
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
const { MonitorType } = require("./monitor-type");
|
const { MonitorType } = require("./monitor-type");
|
||||||
const { log, UP } = require("../../src/util");
|
const {
|
||||||
|
log,
|
||||||
|
UP,
|
||||||
|
} = require("../../src/util");
|
||||||
const mqtt = require("mqtt");
|
const mqtt = require("mqtt");
|
||||||
const jsonata = require("jsonata");
|
const jsonata = require("jsonata");
|
||||||
|
|
||||||
|
@ -57,7 +60,12 @@ class MqttMonitorType extends MonitorType {
|
||||||
*/
|
*/
|
||||||
mqttAsync(hostname, topic, options = {}) {
|
mqttAsync(hostname, topic, options = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const { port, username, password, interval = 20 } = options;
|
const {
|
||||||
|
port,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
interval = 20,
|
||||||
|
} = options;
|
||||||
|
|
||||||
// Adds MQTT protocol to the hostname if not already present
|
// Adds MQTT protocol to the hostname if not already present
|
||||||
if (!/^(?:http|mqtt|ws)s?:\/\//.test(hostname)) {
|
if (!/^(?:http|mqtt|ws)s?:\/\//.test(hostname)) {
|
||||||
|
@ -77,7 +85,7 @@ class MqttMonitorType extends MonitorType {
|
||||||
let client = mqtt.connect(mqttUrl, {
|
let client = mqtt.connect(mqttUrl, {
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
clientId: "uptime-kuma_" + Math.random().toString(16).substr(2, 8)
|
clientId: "uptime-kuma_" + Math.random().toString(16).substr(2, 8),
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on("connect", () => {
|
client.on("connect", () => {
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
const { MonitorType } = require("./monitor-type");
|
const { MonitorType } = require("./monitor-type");
|
||||||
const { log, UP, DOWN } = require("../../src/util");
|
const {
|
||||||
|
log,
|
||||||
|
UP,
|
||||||
|
DOWN,
|
||||||
|
} = require("../../src/util");
|
||||||
const { axiosAbortSignal } = require("../util-server");
|
const { axiosAbortSignal } = require("../util-server");
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
|
|
||||||
|
@ -21,7 +25,7 @@ class RabbitMqMonitorType extends MonitorType {
|
||||||
for (let baseUrl of baseUrls) {
|
for (let baseUrl of baseUrls) {
|
||||||
try {
|
try {
|
||||||
// Without a trailing slash, path in baseUrl will be removed. https://example.com/api -> https://example.com
|
// Without a trailing slash, path in baseUrl will be removed. https://example.com/api -> https://example.com
|
||||||
if ( !baseUrl.endsWith("/") ) {
|
if (!baseUrl.endsWith("/")) {
|
||||||
baseUrl += "/";
|
baseUrl += "/";
|
||||||
}
|
}
|
||||||
const options = {
|
const options = {
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
const { MonitorType } = require("./monitor-type");
|
const { MonitorType } = require("./monitor-type");
|
||||||
const { chromium } = require("playwright-core");
|
const { chromium } = require("playwright-core");
|
||||||
const { UP, log } = require("../../src/util");
|
const {
|
||||||
|
UP,
|
||||||
|
log,
|
||||||
|
} = require("../../src/util");
|
||||||
const { Settings } = require("../settings");
|
const { Settings } = require("../settings");
|
||||||
const commandExistsSync = require("command-exists").sync;
|
const commandExistsSync = require("command-exists").sync;
|
||||||
const childProcess = require("child_process");
|
const childProcess = require("child_process");
|
||||||
|
@ -122,7 +125,7 @@ async function prepareChromeExecutable(executablePath) {
|
||||||
executablePath = "/usr/bin/chromium";
|
executablePath = "/usr/bin/chromium";
|
||||||
|
|
||||||
// Install chromium in container via apt install
|
// Install chromium in container via apt install
|
||||||
if ( !commandExistsSync(executablePath)) {
|
if (!commandExistsSync(executablePath)) {
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
log.info("Chromium", "Installing Chromium...");
|
log.info("Chromium", "Installing Chromium...");
|
||||||
let child = childProcess.exec("apt update && apt --yes --no-install-recommends install chromium fonts-indic fonts-noto fonts-noto-cjk");
|
let child = childProcess.exec("apt update && apt --yes --no-install-recommends install chromium fonts-indic fonts-noto fonts-noto-cjk");
|
||||||
|
@ -213,6 +216,7 @@ async function testChrome(executablePath) {
|
||||||
throw new Error(e.message);
|
throw new Error(e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// test remote browser
|
// test remote browser
|
||||||
/**
|
/**
|
||||||
* @param {string} remoteBrowserURL Remote Browser URL
|
* @param {string} remoteBrowserURL Remote Browser URL
|
||||||
|
@ -228,6 +232,7 @@ async function testRemoteBrowser(remoteBrowserURL) {
|
||||||
throw new Error(e.message);
|
throw new Error(e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RealBrowserMonitorType extends MonitorType {
|
class RealBrowserMonitorType extends MonitorType {
|
||||||
|
|
||||||
name = "real-browser";
|
name = "real-browser";
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
const { MonitorType } = require("./monitor-type");
|
const { MonitorType } = require("./monitor-type");
|
||||||
const { UP, log, evaluateJsonQuery } = require("../../src/util");
|
const {
|
||||||
|
UP,
|
||||||
|
log,
|
||||||
|
evaluateJsonQuery,
|
||||||
|
} = require("../../src/util");
|
||||||
const snmp = require("net-snmp");
|
const snmp = require("net-snmp");
|
||||||
|
|
||||||
class SNMPMonitorType extends MonitorType {
|
class SNMPMonitorType extends MonitorType {
|
||||||
|
@ -42,7 +46,10 @@ class SNMPMonitorType extends MonitorType {
|
||||||
// We restrict querying to one OID per monitor, therefore `varbinds[0]` will always contain the value we're interested in.
|
// We restrict querying to one OID per monitor, therefore `varbinds[0]` will always contain the value we're interested in.
|
||||||
const value = varbinds[0].value;
|
const value = varbinds[0].value;
|
||||||
|
|
||||||
const { status, response } = await evaluateJsonQuery(value, monitor.jsonPath, monitor.jsonPathOperator, monitor.expectedValue);
|
const {
|
||||||
|
status,
|
||||||
|
response,
|
||||||
|
} = await evaluateJsonQuery(value, monitor.jsonPath, monitor.jsonPathOperator, monitor.expectedValue);
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
heartbeat.status = UP;
|
heartbeat.status = UP;
|
||||||
|
|
81
server/notification-providers/gov-notify.js
Normal file
81
server/notification-providers/gov-notify.js
Normal 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;
|
|
@ -20,19 +20,21 @@ class HomeAssistant extends NotificationProvider {
|
||||||
{
|
{
|
||||||
title: "Uptime Kuma",
|
title: "Uptime Kuma",
|
||||||
message: msg,
|
message: msg,
|
||||||
...(notificationService !== "persistent_notification" && { data: {
|
...(notificationService !== "persistent_notification" && {
|
||||||
name: monitorJSON?.name,
|
data: {
|
||||||
status: heartbeatJSON?.status,
|
name: monitorJSON?.name,
|
||||||
channel: "Uptime Kuma",
|
status: heartbeatJSON?.status,
|
||||||
icon_url: "https://github.com/louislam/uptime-kuma/blob/master/public/icon.png?raw=true",
|
channel: "Uptime Kuma",
|
||||||
} }),
|
icon_url: "https://github.com/louislam/uptime-kuma/blob/master/public/icon.png?raw=true",
|
||||||
|
},
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${notification.longLivedAccessToken}`,
|
Authorization: `Bearer ${notification.longLivedAccessToken}`,
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return okMsg;
|
return okMsg;
|
||||||
|
|
|
@ -69,6 +69,7 @@ const Cellsynt = require("./notification-providers/cellsynt");
|
||||||
const Onesender = require("./notification-providers/onesender");
|
const Onesender = require("./notification-providers/onesender");
|
||||||
const Wpush = require("./notification-providers/wpush");
|
const Wpush = require("./notification-providers/wpush");
|
||||||
const SendGrid = require("./notification-providers/send-grid");
|
const SendGrid = require("./notification-providers/send-grid");
|
||||||
|
const GovNotify = require("./notification-providers/gov-notify");
|
||||||
|
|
||||||
class Notification {
|
class Notification {
|
||||||
|
|
||||||
|
@ -154,10 +155,11 @@ class Notification {
|
||||||
new GtxMessaging(),
|
new GtxMessaging(),
|
||||||
new Cellsynt(),
|
new Cellsynt(),
|
||||||
new Wpush(),
|
new Wpush(),
|
||||||
new SendGrid()
|
new SendGrid(),
|
||||||
|
new GovNotify(),
|
||||||
];
|
];
|
||||||
for (let item of list) {
|
for (let item of list) {
|
||||||
if (! item.name) {
|
if (!item.name) {
|
||||||
throw new Error("Notification provider without name");
|
throw new Error("Notification provider without name");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +183,7 @@ class Notification {
|
||||||
if (this.providerList[notification.type]) {
|
if (this.providerList[notification.type]) {
|
||||||
return this.providerList[notification.type].send(notification, msg, monitorJSON, heartbeatJSON);
|
return this.providerList[notification.type].send(notification, msg, monitorJSON, heartbeatJSON);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Notification type is not supported");
|
throw new Error(`Notification type <${notification.type}> is not supported`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,7 +203,7 @@ class Notification {
|
||||||
userID,
|
userID,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (! bean) {
|
if (!bean) {
|
||||||
throw new Error("notification not found");
|
throw new Error("notification not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,7 +236,7 @@ class Notification {
|
||||||
userID,
|
userID,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (! bean) {
|
if (!bean) {
|
||||||
throw new Error("notification not found");
|
throw new Error("notification not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,7 +263,7 @@ class Notification {
|
||||||
*/
|
*/
|
||||||
async function applyNotificationEveryMonitor(notificationID, userID) {
|
async function applyNotificationEveryMonitor(notificationID, userID) {
|
||||||
let monitors = await R.getAll("SELECT id FROM monitor WHERE user_id = ?", [
|
let monitors = await R.getAll("SELECT id FROM monitor WHERE user_id = ?", [
|
||||||
userID
|
userID,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
for (let i = 0; i < monitors.length; i++) {
|
for (let i = 0; i < monitors.length; i++) {
|
||||||
|
@ -270,7 +272,7 @@ async function applyNotificationEveryMonitor(notificationID, userID) {
|
||||||
notificationID,
|
notificationID,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (! checkNotification) {
|
if (!checkNotification) {
|
||||||
let relation = R.dispense("monitor_notification");
|
let relation = R.dispense("monitor_notification");
|
||||||
relation.monitor_id = monitors[i].id;
|
relation.monitor_id = monitors[i].id;
|
||||||
relation.notification_id = notificationID;
|
relation.notification_id = notificationID;
|
||||||
|
|
|
@ -12,24 +12,24 @@ const commonLabels = [
|
||||||
const monitorCertDaysRemaining = new PrometheusClient.Gauge({
|
const monitorCertDaysRemaining = new PrometheusClient.Gauge({
|
||||||
name: "monitor_cert_days_remaining",
|
name: "monitor_cert_days_remaining",
|
||||||
help: "The number of days remaining until the certificate expires",
|
help: "The number of days remaining until the certificate expires",
|
||||||
labelNames: commonLabels
|
labelNames: commonLabels,
|
||||||
});
|
});
|
||||||
|
|
||||||
const monitorCertIsValid = new PrometheusClient.Gauge({
|
const monitorCertIsValid = new PrometheusClient.Gauge({
|
||||||
name: "monitor_cert_is_valid",
|
name: "monitor_cert_is_valid",
|
||||||
help: "Is the certificate still valid? (1 = Yes, 0= No)",
|
help: "Is the certificate still valid? (1 = Yes, 0= No)",
|
||||||
labelNames: commonLabels
|
labelNames: commonLabels,
|
||||||
});
|
});
|
||||||
const monitorResponseTime = new PrometheusClient.Gauge({
|
const monitorResponseTime = new PrometheusClient.Gauge({
|
||||||
name: "monitor_response_time",
|
name: "monitor_response_time",
|
||||||
help: "Monitor Response Time (ms)",
|
help: "Monitor Response Time (ms)",
|
||||||
labelNames: commonLabels
|
labelNames: commonLabels,
|
||||||
});
|
});
|
||||||
|
|
||||||
const monitorStatus = new PrometheusClient.Gauge({
|
const monitorStatus = new PrometheusClient.Gauge({
|
||||||
name: "monitor_status",
|
name: "monitor_status",
|
||||||
help: "Monitor Status (1 = UP, 0= DOWN, 2= PENDING, 3= MAINTENANCE)",
|
help: "Monitor Status (1 = UP, 0= DOWN, 2= PENDING, 3= MAINTENANCE)",
|
||||||
labelNames: commonLabels
|
labelNames: commonLabels,
|
||||||
});
|
});
|
||||||
|
|
||||||
class Prometheus {
|
class Prometheus {
|
||||||
|
@ -44,7 +44,7 @@ class Prometheus {
|
||||||
monitor_type: monitor.type,
|
monitor_type: monitor.type,
|
||||||
monitor_url: monitor.url,
|
monitor_url: monitor.url,
|
||||||
monitor_hostname: monitor.hostname,
|
monitor_hostname: monitor.hostname,
|
||||||
monitor_port: monitor.port
|
monitor_port: monitor.port,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,5 +119,5 @@ class Prometheus {
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Prometheus
|
Prometheus,
|
||||||
};
|
};
|
||||||
|
|
|
@ -36,7 +36,7 @@ class Proxy {
|
||||||
if (!this.SUPPORTED_PROXY_PROTOCOLS.includes(proxy.protocol)) {
|
if (!this.SUPPORTED_PROXY_PROTOCOLS.includes(proxy.protocol)) {
|
||||||
throw new Error(`
|
throw new Error(`
|
||||||
Unsupported proxy protocol "${proxy.protocol}.
|
Unsupported proxy protocol "${proxy.protocol}.
|
||||||
Supported protocols are ${this.SUPPORTED_PROXY_PROTOCOLS.join(", ")}."`
|
Supported protocols are ${this.SUPPORTED_PROXY_PROTOCOLS.join(", ")}."`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +92,10 @@ class Proxy {
|
||||||
* @throws Proxy protocol is unsupported
|
* @throws Proxy protocol is unsupported
|
||||||
*/
|
*/
|
||||||
static createAgents(proxy, options) {
|
static createAgents(proxy, options) {
|
||||||
const { httpAgentOptions, httpsAgentOptions } = options || {};
|
const {
|
||||||
|
httpAgentOptions,
|
||||||
|
httpsAgentOptions,
|
||||||
|
} = options || {};
|
||||||
let agent;
|
let agent;
|
||||||
let httpAgent;
|
let httpAgent;
|
||||||
let httpsAgent;
|
let httpsAgent;
|
||||||
|
@ -150,12 +153,13 @@ class Proxy {
|
||||||
httpsAgent = agent;
|
httpsAgent = agent;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: throw new Error(`Unsupported proxy protocol provided. ${proxy.protocol}`);
|
default:
|
||||||
|
throw new Error(`Unsupported proxy protocol provided. ${proxy.protocol}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
httpAgent,
|
httpAgent,
|
||||||
httpsAgent
|
httpsAgent,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,15 @@ const { R } = require("redbean-node");
|
||||||
const apicache = require("../modules/apicache");
|
const apicache = require("../modules/apicache");
|
||||||
const Monitor = require("../model/monitor");
|
const Monitor = require("../model/monitor");
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
const { UP, MAINTENANCE, DOWN, PENDING, flipStatus, log, badgeConstants } = require("../../src/util");
|
const {
|
||||||
|
UP,
|
||||||
|
MAINTENANCE,
|
||||||
|
DOWN,
|
||||||
|
PENDING,
|
||||||
|
flipStatus,
|
||||||
|
log,
|
||||||
|
badgeConstants,
|
||||||
|
} = require("../../src/util");
|
||||||
const StatusPage = require("../model/status_page");
|
const StatusPage = require("../model/status_page");
|
||||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||||
const { makeBadge } = require("badge-maker");
|
const { makeBadge } = require("badge-maker");
|
||||||
|
@ -28,7 +36,7 @@ let io = server.io;
|
||||||
router.get("/api/entry-page", async (request, response) => {
|
router.get("/api/entry-page", async (request, response) => {
|
||||||
allowDevAllOrigin(response);
|
allowDevAllOrigin(response);
|
||||||
|
|
||||||
let result = { };
|
let result = {};
|
||||||
let hostname = request.hostname;
|
let hostname = request.hostname;
|
||||||
if ((await setting("trustProxy")) && request.headers["x-forwarded-host"]) {
|
if ((await setting("trustProxy")) && request.headers["x-forwarded-host"]) {
|
||||||
hostname = request.headers["x-forwarded-host"];
|
hostname = request.headers["x-forwarded-host"];
|
||||||
|
@ -53,10 +61,10 @@ router.all("/api/push/:pushToken", async (request, response) => {
|
||||||
let status = (statusString === "up") ? UP : DOWN;
|
let status = (statusString === "up") ? UP : DOWN;
|
||||||
|
|
||||||
let monitor = await R.findOne("monitor", " push_token = ? AND active = 1 ", [
|
let monitor = await R.findOne("monitor", " push_token = ? AND active = 1 ", [
|
||||||
pushToken
|
pushToken,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (! monitor) {
|
if (!monitor) {
|
||||||
throw new Error("Monitor not found or not active.");
|
throw new Error("Monitor not found or not active.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +135,7 @@ router.all("/api/push/:pushToken", async (request, response) => {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
response.status(404).json({
|
response.status(404).json({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: e.message
|
msg: e.message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -159,7 +167,7 @@ router.get("/api/badge/:id/status", cache("5 minutes"), async (request, response
|
||||||
AND monitor_group.monitor_id = ?
|
AND monitor_group.monitor_id = ?
|
||||||
AND public = 1
|
AND public = 1
|
||||||
`,
|
`,
|
||||||
[ requestedMonitorId ]
|
[ requestedMonitorId ],
|
||||||
);
|
);
|
||||||
|
|
||||||
const badgeValues = { style };
|
const badgeValues = { style };
|
||||||
|
@ -242,7 +250,7 @@ router.get("/api/badge/:id/uptime/:duration?", cache("5 minutes"), async (reques
|
||||||
AND monitor_group.monitor_id = ?
|
AND monitor_group.monitor_id = ?
|
||||||
AND public = 1
|
AND public = 1
|
||||||
`,
|
`,
|
||||||
[ requestedMonitorId ]
|
[ requestedMonitorId ],
|
||||||
);
|
);
|
||||||
|
|
||||||
const badgeValues = { style };
|
const badgeValues = { style };
|
||||||
|
@ -362,7 +370,7 @@ router.get("/api/badge/:id/avg-response/:duration?", cache("5 minutes"), async (
|
||||||
request.params.duration
|
request.params.duration
|
||||||
? parseInt(request.params.duration, 10)
|
? parseInt(request.params.duration, 10)
|
||||||
: 24,
|
: 24,
|
||||||
720
|
720,
|
||||||
);
|
);
|
||||||
const overrideValue = value && parseFloat(value);
|
const overrideValue = value && parseFloat(value);
|
||||||
|
|
||||||
|
@ -376,7 +384,7 @@ router.get("/api/badge/:id/avg-response/:duration?", cache("5 minutes"), async (
|
||||||
AND public = 1
|
AND public = 1
|
||||||
AND heartbeat.monitor_id = ?
|
AND heartbeat.monitor_id = ?
|
||||||
`,
|
`,
|
||||||
[ -requestedDuration, requestedMonitorId ]
|
[ -requestedDuration, requestedMonitorId ],
|
||||||
));
|
));
|
||||||
|
|
||||||
const badgeValues = { style };
|
const badgeValues = { style };
|
||||||
|
@ -443,7 +451,7 @@ router.get("/api/badge/:id/cert-exp", cache("5 minutes"), async (request, respon
|
||||||
AND monitor_group.monitor_id = ?
|
AND monitor_group.monitor_id = ?
|
||||||
AND public = 1
|
AND public = 1
|
||||||
`,
|
`,
|
||||||
[ requestedMonitorId ]
|
[ requestedMonitorId ],
|
||||||
);
|
);
|
||||||
|
|
||||||
const badgeValues = { style };
|
const badgeValues = { style };
|
||||||
|
@ -528,7 +536,7 @@ router.get("/api/badge/:id/response", cache("5 minutes"), async (request, respon
|
||||||
AND monitor_group.monitor_id = ?
|
AND monitor_group.monitor_id = ?
|
||||||
AND public = 1
|
AND public = 1
|
||||||
`,
|
`,
|
||||||
[ requestedMonitorId ]
|
[ requestedMonitorId ],
|
||||||
);
|
);
|
||||||
|
|
||||||
const badgeValues = { style };
|
const badgeValues = { style };
|
||||||
|
@ -540,7 +548,7 @@ router.get("/api/badge/:id/response", cache("5 minutes"), async (request, respon
|
||||||
badgeValues.color = badgeConstants.naColor;
|
badgeValues.color = badgeConstants.naColor;
|
||||||
} else {
|
} else {
|
||||||
const heartbeat = await Monitor.getPreviousHeartbeat(
|
const heartbeat = await Monitor.getPreviousHeartbeat(
|
||||||
requestedMonitorId
|
requestedMonitorId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!heartbeat.ping) {
|
if (!heartbeat.ping) {
|
||||||
|
|
|
@ -2,7 +2,10 @@ let express = require("express");
|
||||||
const apicache = require("../modules/apicache");
|
const apicache = require("../modules/apicache");
|
||||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||||
const StatusPage = require("../model/status_page");
|
const StatusPage = require("../model/status_page");
|
||||||
const { allowDevAllOrigin, sendHttpError } = require("../util-server");
|
const {
|
||||||
|
allowDevAllOrigin,
|
||||||
|
sendHttpError,
|
||||||
|
} = require("../util-server");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { badgeConstants } = require("../../src/util");
|
const { badgeConstants } = require("../../src/util");
|
||||||
const { makeBadge } = require("badge-maker");
|
const { makeBadge } = require("badge-maker");
|
||||||
|
@ -44,7 +47,7 @@ router.get("/api/status-page/:slug", cache("5 minutes"), async (request, respons
|
||||||
try {
|
try {
|
||||||
// Get Status Page
|
// Get Status Page
|
||||||
let statusPage = await R.findOne("status_page", " slug = ? ", [
|
let statusPage = await R.findOne("status_page", " slug = ? ", [
|
||||||
slug
|
slug,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!statusPage) {
|
if (!statusPage) {
|
||||||
|
@ -81,7 +84,7 @@ router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (reques
|
||||||
AND public = 1
|
AND public = 1
|
||||||
AND \`group\`.status_page_id = ?
|
AND \`group\`.status_page_id = ?
|
||||||
`, [
|
`, [
|
||||||
statusPageID
|
statusPageID,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
for (let monitorID of monitorIDList) {
|
for (let monitorID of monitorIDList) {
|
||||||
|
@ -103,7 +106,7 @@ router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (reques
|
||||||
|
|
||||||
response.json({
|
response.json({
|
||||||
heartbeatList,
|
heartbeatList,
|
||||||
uptimeList
|
uptimeList,
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -120,7 +123,7 @@ router.get("/api/status-page/:slug/manifest.json", cache("1440 minutes"), async
|
||||||
try {
|
try {
|
||||||
// Get Status Page
|
// Get Status Page
|
||||||
let statusPage = await R.findOne("status_page", " slug = ? ", [
|
let statusPage = await R.findOne("status_page", " slug = ? ", [
|
||||||
slug
|
slug,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!statusPage) {
|
if (!statusPage) {
|
||||||
|
@ -137,9 +140,9 @@ router.get("/api/status-page/:slug/manifest.json", cache("1440 minutes"), async
|
||||||
{
|
{
|
||||||
"src": statusPage.icon,
|
"src": statusPage.icon,
|
||||||
"sizes": "128x128",
|
"sizes": "128x128",
|
||||||
"type": "image/png"
|
"type": "image/png",
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -159,7 +162,7 @@ router.get("/api/status-page/:slug/badge", cache("5 minutes"), async (request, r
|
||||||
downColor = badgeConstants.defaultDownColor,
|
downColor = badgeConstants.defaultDownColor,
|
||||||
partialColor = "#F6BE00",
|
partialColor = "#F6BE00",
|
||||||
maintenanceColor = "#808080",
|
maintenanceColor = "#808080",
|
||||||
style = badgeConstants.defaultStyle
|
style = badgeConstants.defaultStyle,
|
||||||
} = request.query;
|
} = request.query;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -169,7 +172,7 @@ router.get("/api/status-page/:slug/badge", cache("5 minutes"), async (request, r
|
||||||
AND public = 1
|
AND public = 1
|
||||||
AND \`group\`.status_page_id = ?
|
AND \`group\`.status_page_id = ?
|
||||||
`, [
|
`, [
|
||||||
statusPageID
|
statusPageID,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let hasUp = false;
|
let hasUp = false;
|
||||||
|
|
|
@ -37,13 +37,19 @@ if (!semver.satisfies(nodeVersion, requiredNodeVersions)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const args = require("args-parser")(process.argv);
|
const args = require("args-parser")(process.argv);
|
||||||
const { sleep, log, getRandomInt, genSecret, isDev } = require("../src/util");
|
const {
|
||||||
|
sleep,
|
||||||
|
log,
|
||||||
|
getRandomInt,
|
||||||
|
genSecret,
|
||||||
|
isDev,
|
||||||
|
} = require("../src/util");
|
||||||
const config = require("./config");
|
const config = require("./config");
|
||||||
|
|
||||||
log.debug("server", "Arguments");
|
log.debug("server", "Arguments");
|
||||||
log.debug("server", args);
|
log.debug("server", args);
|
||||||
|
|
||||||
if (! process.env.NODE_ENV) {
|
if (!process.env.NODE_ENV) {
|
||||||
process.env.NODE_ENV = "production";
|
process.env.NODE_ENV = "production";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +96,16 @@ const Monitor = require("./model/monitor");
|
||||||
const User = require("./model/user");
|
const User = require("./model/user");
|
||||||
|
|
||||||
log.debug("server", "Importing Settings");
|
log.debug("server", "Importing Settings");
|
||||||
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, doubleCheckPassword, shake256, SHAKE256_LENGTH, allowDevAllOrigin,
|
const {
|
||||||
|
getSettings,
|
||||||
|
setSettings,
|
||||||
|
setting,
|
||||||
|
initJWTSecret,
|
||||||
|
checkLogin,
|
||||||
|
doubleCheckPassword,
|
||||||
|
shake256,
|
||||||
|
SHAKE256_LENGTH,
|
||||||
|
allowDevAllOrigin,
|
||||||
} = require("./util-server");
|
} = require("./util-server");
|
||||||
|
|
||||||
log.debug("server", "Importing Notification");
|
log.debug("server", "Importing Notification");
|
||||||
|
@ -101,8 +116,14 @@ log.debug("server", "Importing Database");
|
||||||
const Database = require("./database");
|
const Database = require("./database");
|
||||||
|
|
||||||
log.debug("server", "Importing Background Jobs");
|
log.debug("server", "Importing Background Jobs");
|
||||||
const { initBackgroundJobs, stopBackgroundJobs } = require("./jobs");
|
const {
|
||||||
const { loginRateLimiter, twoFaRateLimiter } = require("./rate-limiter");
|
initBackgroundJobs,
|
||||||
|
stopBackgroundJobs,
|
||||||
|
} = require("./jobs");
|
||||||
|
const {
|
||||||
|
loginRateLimiter,
|
||||||
|
twoFaRateLimiter,
|
||||||
|
} = require("./rate-limiter");
|
||||||
|
|
||||||
const { apiAuth } = require("./auth");
|
const { apiAuth } = require("./auth");
|
||||||
const { login } = require("./auth");
|
const { login } = require("./auth");
|
||||||
|
@ -122,7 +143,7 @@ const cloudflaredToken = args["cloudflared-token"] || process.env.UPTIME_KUMA_CL
|
||||||
// 2FA / notp verification defaults
|
// 2FA / notp verification defaults
|
||||||
const twoFAVerifyOptions = {
|
const twoFAVerifyOptions = {
|
||||||
"window": 1,
|
"window": 1,
|
||||||
"time": 30
|
"time": 30,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -132,13 +153,26 @@ const twoFAVerifyOptions = {
|
||||||
const testMode = !!args["test"] || false;
|
const testMode = !!args["test"] || false;
|
||||||
|
|
||||||
// Must be after io instantiation
|
// Must be after io instantiation
|
||||||
const { sendNotificationList, sendHeartbeatList, sendInfo, sendProxyList, sendDockerHostList, sendAPIKeyList, sendRemoteBrowserList, sendMonitorTypeList } = require("./client");
|
const {
|
||||||
|
sendNotificationList,
|
||||||
|
sendHeartbeatList,
|
||||||
|
sendInfo,
|
||||||
|
sendProxyList,
|
||||||
|
sendDockerHostList,
|
||||||
|
sendAPIKeyList,
|
||||||
|
sendRemoteBrowserList,
|
||||||
|
sendMonitorTypeList,
|
||||||
|
} = require("./client");
|
||||||
const { statusPageSocketHandler } = require("./socket-handlers/status-page-socket-handler");
|
const { statusPageSocketHandler } = require("./socket-handlers/status-page-socket-handler");
|
||||||
const { databaseSocketHandler } = require("./socket-handlers/database-socket-handler");
|
const { databaseSocketHandler } = require("./socket-handlers/database-socket-handler");
|
||||||
const { remoteBrowserSocketHandler } = require("./socket-handlers/remote-browser-socket-handler");
|
const { remoteBrowserSocketHandler } = require("./socket-handlers/remote-browser-socket-handler");
|
||||||
const TwoFA = require("./2fa");
|
const TwoFA = require("./2fa");
|
||||||
const StatusPage = require("./model/status_page");
|
const StatusPage = require("./model/status_page");
|
||||||
const { cloudflaredSocketHandler, autoStart: cloudflaredAutoStart, stop: cloudflaredStop } = require("./socket-handlers/cloudflared-socket-handler");
|
const {
|
||||||
|
cloudflaredSocketHandler,
|
||||||
|
autoStart: cloudflaredAutoStart,
|
||||||
|
stop: cloudflaredStop,
|
||||||
|
} = require("./socket-handlers/cloudflared-socket-handler");
|
||||||
const { proxySocketHandler } = require("./socket-handlers/proxy-socket-handler");
|
const { proxySocketHandler } = require("./socket-handlers/proxy-socket-handler");
|
||||||
const { dockerSocketHandler } = require("./socket-handlers/docker-socket-handler");
|
const { dockerSocketHandler } = require("./socket-handlers/docker-socket-handler");
|
||||||
const { maintenanceSocketHandler } = require("./socket-handlers/maintenance-socket-handler");
|
const { maintenanceSocketHandler } = require("./socket-handlers/maintenance-socket-handler");
|
||||||
|
@ -933,8 +967,9 @@ let needSetup = false;
|
||||||
monitorID,
|
monitorID,
|
||||||
socket.userID,
|
socket.userID,
|
||||||
]);
|
]);
|
||||||
const monitorData = [{ id: monitor.id,
|
const monitorData = [{
|
||||||
active: monitor.active
|
id: monitor.id,
|
||||||
|
active: monitor.active,
|
||||||
}];
|
}];
|
||||||
const preloadData = await Monitor.preparePreloadData(monitorData);
|
const preloadData = await Monitor.preparePreloadData(monitorData);
|
||||||
callback({
|
callback({
|
||||||
|
@ -966,7 +1001,8 @@ let needSetup = false;
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM heartbeat
|
FROM heartbeat
|
||||||
WHERE monitor_id = ?
|
WHERE monitor_id = ?
|
||||||
AND time > ${sqlHourOffset}
|
AND time
|
||||||
|
> ${sqlHourOffset}
|
||||||
ORDER BY time ASC
|
ORDER BY time ASC
|
||||||
`, [
|
`, [
|
||||||
monitorID,
|
monitorID,
|
||||||
|
@ -1519,7 +1555,7 @@ let needSetup = false;
|
||||||
log.info("manage", `Clear Heartbeats Monitor: ${monitorID} User ID: ${socket.userID}`);
|
log.info("manage", `Clear Heartbeats Monitor: ${monitorID} User ID: ${socket.userID}`);
|
||||||
|
|
||||||
await R.exec("DELETE FROM heartbeat WHERE monitor_id = ?", [
|
await R.exec("DELETE FROM heartbeat WHERE monitor_id = ?", [
|
||||||
monitorID
|
monitorID,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await sendHeartbeatList(socket, monitorID, true, true);
|
await sendHeartbeatList(socket, monitorID, true, true);
|
||||||
|
@ -1658,7 +1694,7 @@ async function checkOwner(userID, monitorID) {
|
||||||
userID,
|
userID,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (! row) {
|
if (!row) {
|
||||||
throw new Error("You do not own this monitor.");
|
throw new Error("You do not own this monitor.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1698,7 +1734,7 @@ async function afterLogin(socket, user) {
|
||||||
|
|
||||||
// Set server timezone from client browser if not set
|
// Set server timezone from client browser if not set
|
||||||
// It should be run once only
|
// It should be run once only
|
||||||
if (! await Settings.get("initServerTimezone")) {
|
if (!await Settings.get("initServerTimezone")) {
|
||||||
log.debug("server", "emit initServerTimezone");
|
log.debug("server", "emit initServerTimezone");
|
||||||
socket.emit("initServerTimezone");
|
socket.emit("initServerTimezone");
|
||||||
}
|
}
|
||||||
|
@ -1722,7 +1758,7 @@ async function initDatabase(testMode = false) {
|
||||||
"jwtSecret",
|
"jwtSecret",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (! jwtSecretBean) {
|
if (!jwtSecretBean) {
|
||||||
log.info("server", "JWT secret is not found, generate one.");
|
log.info("server", "JWT secret is not found, generate one.");
|
||||||
jwtSecretBean = await initJWTSecret();
|
jwtSecretBean = await initJWTSecret();
|
||||||
log.info("server", "Stored JWT secret into database");
|
log.info("server", "Stored JWT secret into database");
|
||||||
|
|
|
@ -17,9 +17,7 @@ class Settings {
|
||||||
* }
|
* }
|
||||||
* @type {{}}
|
* @type {{}}
|
||||||
*/
|
*/
|
||||||
static cacheList = {
|
static cacheList = {};
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
static cacheCleaner = null;
|
static cacheCleaner = null;
|
||||||
|
|
||||||
|
@ -61,7 +59,7 @@ class Settings {
|
||||||
|
|
||||||
Settings.cacheList[key] = {
|
Settings.cacheList[key] = {
|
||||||
value: v,
|
value: v,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
|
@ -129,7 +127,7 @@ class Settings {
|
||||||
|
|
||||||
for (let key of keyList) {
|
for (let key of keyList) {
|
||||||
let bean = await R.findOne("setting", " `key` = ? ", [
|
let bean = await R.findOne("setting", " `key` = ? ", [
|
||||||
key
|
key,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (bean == null) {
|
if (bean == null) {
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
const tcpp = require("tcp-ping");
|
const tcpp = require("tcp-ping");
|
||||||
const ping = require("@louislam/ping");
|
const ping = require("@louislam/ping");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { log, genSecret, badgeConstants } = require("../src/util");
|
const {
|
||||||
|
log,
|
||||||
|
genSecret,
|
||||||
|
badgeConstants,
|
||||||
|
} = require("../src/util");
|
||||||
const passwordHash = require("./password-hash");
|
const passwordHash = require("./password-hash");
|
||||||
const { Resolver } = require("dns");
|
const { Resolver } = require("dns");
|
||||||
const iconv = require("iconv-lite");
|
const iconv = require("iconv-lite");
|
||||||
|
@ -22,14 +26,20 @@ const tls = require("tls");
|
||||||
|
|
||||||
const {
|
const {
|
||||||
dictionaries: {
|
dictionaries: {
|
||||||
rfc2865: { file, attributes },
|
rfc2865: {
|
||||||
|
file,
|
||||||
|
attributes,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} = require("node-radius-utils");
|
} = require("node-radius-utils");
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
|
|
||||||
// SASLOptions used in JSDoc
|
// SASLOptions used in JSDoc
|
||||||
// eslint-disable-next-line no-unused-vars
|
const {
|
||||||
const { Kafka, SASLOptions } = require("kafkajs");
|
Kafka,
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
SASLOptions,
|
||||||
|
} = require("kafkajs");
|
||||||
const crypto = require("crypto");
|
const crypto = require("crypto");
|
||||||
|
|
||||||
const isWindows = process.platform === /^win/.test(process.platform);
|
const isWindows = process.platform === /^win/.test(process.platform);
|
||||||
|
@ -75,7 +85,7 @@ exports.getOidcTokenClientCredentials = async (tokenEndpoint, clientId, clientSe
|
||||||
let client = new oauthProvider.Client({
|
let client = new oauthProvider.Client({
|
||||||
client_id: clientId,
|
client_id: clientId,
|
||||||
client_secret: clientSecret,
|
client_secret: clientSecret,
|
||||||
token_endpoint_auth_method: authMethod
|
token_endpoint_auth_method: authMethod,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Increase default timeout and clock tolerance
|
// Increase default timeout and clock tolerance
|
||||||
|
@ -185,7 +195,12 @@ exports.pingAsync = function (hostname, ipv6 = false, size = 56) {
|
||||||
*/
|
*/
|
||||||
exports.kafkaProducerAsync = function (brokers, topic, message, options = {}, saslOptions = {}) {
|
exports.kafkaProducerAsync = function (brokers, topic, message, options = {}, saslOptions = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const { interval = 20, allowAutoTopicCreation = false, ssl = false, clientId = "Uptime-Kuma" } = options;
|
const {
|
||||||
|
interval = 20,
|
||||||
|
allowAutoTopicCreation = false,
|
||||||
|
ssl = false,
|
||||||
|
clientId = "Uptime-Kuma",
|
||||||
|
} = options;
|
||||||
|
|
||||||
let connectedToKafka = false;
|
let connectedToKafka = false;
|
||||||
|
|
||||||
|
@ -213,7 +228,7 @@ exports.kafkaProducerAsync = function (brokers, topic, message, options = {}, sa
|
||||||
allowAutoTopicCreation: allowAutoTopicCreation,
|
allowAutoTopicCreation: allowAutoTopicCreation,
|
||||||
retry: {
|
retry: {
|
||||||
retries: 0,
|
retries: 0,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
producer.connect().then(
|
producer.connect().then(
|
||||||
|
@ -234,14 +249,14 @@ exports.kafkaProducerAsync = function (brokers, topic, message, options = {}, sa
|
||||||
connectedToKafka = true;
|
connectedToKafka = true;
|
||||||
clearTimeout(timeoutID);
|
clearTimeout(timeoutID);
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
).catch(
|
).catch(
|
||||||
(e) => {
|
(e) => {
|
||||||
connectedToKafka = true;
|
connectedToKafka = true;
|
||||||
producer.disconnect();
|
producer.disconnect();
|
||||||
clearTimeout(timeoutID);
|
clearTimeout(timeoutID);
|
||||||
reject(new Error("Error in producer connection: " + e.message));
|
reject(new Error("Error in producer connection: " + e.message));
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
producer.on("producer.network.request_timeout", (_) => {
|
producer.on("producer.network.request_timeout", (_) => {
|
||||||
|
@ -409,7 +424,7 @@ exports.mysqlQuery = function (connectionString, query, password = undefined) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const connection = mysql.createConnection({
|
const connection = mysql.createConnection({
|
||||||
uri: connectionString,
|
uri: connectionString,
|
||||||
password
|
password,
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.on("error", (err) => {
|
connection.on("error", (err) => {
|
||||||
|
@ -494,8 +509,8 @@ exports.redisPingAsync = function (dsn, rejectUnauthorized) {
|
||||||
const client = redis.createClient({
|
const client = redis.createClient({
|
||||||
url: dsn,
|
url: dsn,
|
||||||
socket: {
|
socket: {
|
||||||
rejectUnauthorized
|
rejectUnauthorized,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
client.on("error", (err) => {
|
client.on("error", (err) => {
|
||||||
if (client.isOpen) {
|
if (client.isOpen) {
|
||||||
|
@ -661,7 +676,7 @@ exports.checkCertificate = function (socket) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
valid: valid,
|
valid: valid,
|
||||||
certInfo: parsedInfo
|
certInfo: parsedInfo,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -693,7 +708,7 @@ exports.checkStatusCode = function (status, acceptedCodes) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.error("monitor", `${codeRange} is not a valid status code range`);
|
log.error("monitor", `${codeRange} is not a valid status code range`);
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -925,14 +940,21 @@ module.exports.timeObjectToLocal = (obj, timezone = undefined) => {
|
||||||
* @returns {Promise<object>} Result of gRPC query
|
* @returns {Promise<object>} Result of gRPC query
|
||||||
*/
|
*/
|
||||||
module.exports.grpcQuery = async (options) => {
|
module.exports.grpcQuery = async (options) => {
|
||||||
const { grpcUrl, grpcProtobufData, grpcServiceName, grpcEnableTls, grpcMethod, grpcBody } = options;
|
const {
|
||||||
|
grpcUrl,
|
||||||
|
grpcProtobufData,
|
||||||
|
grpcServiceName,
|
||||||
|
grpcEnableTls,
|
||||||
|
grpcMethod,
|
||||||
|
grpcBody,
|
||||||
|
} = options;
|
||||||
const protocObject = protojs.parse(grpcProtobufData);
|
const protocObject = protojs.parse(grpcProtobufData);
|
||||||
const protoServiceObject = protocObject.root.lookupService(grpcServiceName);
|
const protoServiceObject = protocObject.root.lookupService(grpcServiceName);
|
||||||
const Client = grpc.makeGenericClientConstructor({});
|
const Client = grpc.makeGenericClientConstructor({});
|
||||||
const credentials = grpcEnableTls ? grpc.credentials.createSsl() : grpc.credentials.createInsecure();
|
const credentials = grpcEnableTls ? grpc.credentials.createSsl() : grpc.credentials.createInsecure();
|
||||||
const client = new Client(
|
const client = new Client(
|
||||||
grpcUrl,
|
grpcUrl,
|
||||||
credentials
|
credentials,
|
||||||
);
|
);
|
||||||
const grpcService = protoServiceObject.create(function (method, requestData, cb) {
|
const grpcService = protoServiceObject.create(function (method, requestData, cb) {
|
||||||
const fullServiceName = method.fullName;
|
const fullServiceName = method.fullName;
|
||||||
|
@ -955,14 +977,14 @@ module.exports.grpcQuery = async (options) => {
|
||||||
return resolve({
|
return resolve({
|
||||||
code: err.code,
|
code: err.code,
|
||||||
errorMessage: err.details,
|
errorMessage: err.details,
|
||||||
data: ""
|
data: "",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log.debug("monitor:", `gRPC response: ${JSON.stringify(response)}`);
|
log.debug("monitor:", `gRPC response: ${JSON.stringify(response)}`);
|
||||||
return resolve({
|
return resolve({
|
||||||
code: 1,
|
code: 1,
|
||||||
errorMessage: "",
|
errorMessage: "",
|
||||||
data: responseData
|
data: responseData,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -970,7 +992,7 @@ module.exports.grpcQuery = async (options) => {
|
||||||
return resolve({
|
return resolve({
|
||||||
code: -1,
|
code: -1,
|
||||||
errorMessage: `Error ${err}. Please review your gRPC configuration option. The service name must not include package name value, and the method name must follow camelCase format`,
|
errorMessage: `Error ${err}. Please review your gRPC configuration option. The service name must not include package name value, and the method name must follow camelCase format`,
|
||||||
data: ""
|
data: "",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,5 +81,5 @@ class ArrayWithKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ArrayWithKey
|
ArrayWithKey,
|
||||||
};
|
};
|
||||||
|
|
|
@ -44,5 +44,5 @@ class LimitQueue extends ArrayWithKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
LimitQueue
|
LimitQueue,
|
||||||
};
|
};
|
||||||
|
|
|
@ -121,6 +121,7 @@ export default {
|
||||||
"Elks": "46elks",
|
"Elks": "46elks",
|
||||||
"GoogleChat": "Google Chat (Google Workspace)",
|
"GoogleChat": "Google Chat (Google Workspace)",
|
||||||
"gorush": "Gorush",
|
"gorush": "Gorush",
|
||||||
|
"GovNotify": "GOV Notify",
|
||||||
"gotify": "Gotify",
|
"gotify": "Gotify",
|
||||||
"GrafanaOncall": "Grafana Oncall",
|
"GrafanaOncall": "Grafana Oncall",
|
||||||
"HeiiOnCall": "Heii On-Call",
|
"HeiiOnCall": "Heii On-Call",
|
||||||
|
@ -262,7 +263,7 @@ export default {
|
||||||
this.id = null;
|
this.id = null;
|
||||||
this.notification = {
|
this.notification = {
|
||||||
name: "",
|
name: "",
|
||||||
type: "telegram",
|
type: "GovNotify",
|
||||||
isDefault: false,
|
isDefault: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
75
src/components/notifications/GovNotify.vue
Normal file
75
src/components/notifications/GovNotify.vue
Normal 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>
|
|
@ -67,6 +67,7 @@ import Cellsynt from "./Cellsynt.vue";
|
||||||
import WPush from "./WPush.vue";
|
import WPush from "./WPush.vue";
|
||||||
import SIGNL4 from "./SIGNL4.vue";
|
import SIGNL4 from "./SIGNL4.vue";
|
||||||
import SendGrid from "./SendGrid.vue";
|
import SendGrid from "./SendGrid.vue";
|
||||||
|
import GovNotify from "./GovNotify.vue";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manage all notification form.
|
* Manage all notification form.
|
||||||
|
@ -142,6 +143,7 @@ const NotificationFormList = {
|
||||||
"Cellsynt": Cellsynt,
|
"Cellsynt": Cellsynt,
|
||||||
"WPush": WPush,
|
"WPush": WPush,
|
||||||
"SendGrid": SendGrid,
|
"SendGrid": SendGrid,
|
||||||
|
"GovNotify": GovNotify
|
||||||
};
|
};
|
||||||
|
|
||||||
export default NotificationFormList;
|
export default NotificationFormList;
|
||||||
|
|
Loading…
Reference in a new issue