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