2021-07-27 10:47:13 -07:00
|
|
|
const tcpp = require("tcp-ping");
|
2021-07-01 02:00:23 -07:00
|
|
|
const Ping = require("./ping-lite");
|
2021-07-27 10:47:13 -07:00
|
|
|
const { R } = require("redbean-node");
|
2022-04-13 08:33:37 -07:00
|
|
|
const { log, genSecret } = require("../src/util");
|
2021-08-08 22:34:44 -07:00
|
|
|
const passwordHash = require("./password-hash");
|
2021-08-23 07:30:11 -07:00
|
|
|
const { Resolver } = require("dns");
|
2022-04-13 09:30:32 -07:00
|
|
|
const childProcess = require("child_process");
|
2021-10-13 09:22:49 -07:00
|
|
|
const iconv = require("iconv-lite");
|
|
|
|
const chardet = require("chardet");
|
2021-11-03 18:46:43 -07:00
|
|
|
const mqtt = require("mqtt");
|
2022-01-03 06:48:52 -08:00
|
|
|
const chroma = require("chroma-js");
|
2022-01-04 03:21:53 -08:00
|
|
|
const { badgeConstants } = require("./config");
|
2022-05-13 06:40:46 -07:00
|
|
|
const mssql = require("mssql");
|
2022-06-15 10:12:47 -07:00
|
|
|
const { Client } = require("pg");
|
2022-06-15 11:00:14 -07:00
|
|
|
const postgresConParse = require("pg-connection-string").parse;
|
2022-05-13 10:58:23 -07:00
|
|
|
const { NtlmClient } = require("axios-ntlm");
|
2022-06-28 23:57:40 -07:00
|
|
|
const { Settings } = require("./settings");
|
2022-05-12 02:48:38 -07:00
|
|
|
const radiusClient = require("node-radius-client");
|
|
|
|
const {
|
|
|
|
dictionaries: {
|
|
|
|
rfc2865: { file, attributes },
|
|
|
|
},
|
|
|
|
} = require("node-radius-utils");
|
2022-09-25 04:38:28 -07:00
|
|
|
const dayjs = require("dayjs");
|
2021-08-08 22:34:44 -07:00
|
|
|
|
2021-10-13 23:09:16 -07:00
|
|
|
// From ping-lite
|
|
|
|
exports.WIN = /^win/.test(process.platform);
|
|
|
|
exports.LIN = /^linux/.test(process.platform);
|
|
|
|
exports.MAC = /^darwin/.test(process.platform);
|
|
|
|
exports.FBSD = /^freebsd/.test(process.platform);
|
2022-01-09 08:27:24 -08:00
|
|
|
exports.BSD = /bsd$/.test(process.platform);
|
2021-08-08 22:34:44 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Init or reset JWT secret
|
|
|
|
* @returns {Promise<Bean>}
|
|
|
|
*/
|
|
|
|
exports.initJWTSecret = async () => {
|
|
|
|
let jwtSecretBean = await R.findOne("setting", " `key` = ? ", [
|
|
|
|
"jwtSecret",
|
|
|
|
]);
|
|
|
|
|
2021-11-03 18:46:43 -07:00
|
|
|
if (!jwtSecretBean) {
|
2021-08-08 22:34:44 -07:00
|
|
|
jwtSecretBean = R.dispense("setting");
|
|
|
|
jwtSecretBean.key = "jwtSecret";
|
|
|
|
}
|
|
|
|
|
2022-03-29 02:38:48 -07:00
|
|
|
jwtSecretBean.value = passwordHash.generate(genSecret());
|
2021-08-08 22:34:44 -07:00
|
|
|
await R.store(jwtSecretBean);
|
|
|
|
return jwtSecretBean;
|
2021-09-20 01:22:18 -07:00
|
|
|
};
|
2021-06-30 23:03:06 -07:00
|
|
|
|
2022-04-20 11:56:40 -07:00
|
|
|
/**
|
|
|
|
* Send TCP request to specified hostname and port
|
|
|
|
* @param {string} hostname Hostname / address of machine
|
|
|
|
* @param {number} port TCP port to test
|
|
|
|
* @returns {Promise<number>} Maximum time in ms rounded to nearest integer
|
|
|
|
*/
|
2021-06-30 23:03:06 -07:00
|
|
|
exports.tcping = function (hostname, port) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
tcpp.ping({
|
|
|
|
address: hostname,
|
|
|
|
port: port,
|
|
|
|
attempts: 1,
|
2021-08-05 04:04:38 -07:00
|
|
|
}, function (err, data) {
|
2021-06-30 23:03:06 -07:00
|
|
|
|
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data.results.length >= 1 && data.results[0].err) {
|
|
|
|
reject(data.results[0].err);
|
|
|
|
}
|
|
|
|
|
|
|
|
resolve(Math.round(data.max));
|
|
|
|
});
|
|
|
|
});
|
2021-09-20 01:22:18 -07:00
|
|
|
};
|
2021-07-01 02:00:23 -07:00
|
|
|
|
2022-04-20 11:56:40 -07:00
|
|
|
/**
|
|
|
|
* Ping the specified machine
|
|
|
|
* @param {string} hostname Hostname / address of machine
|
|
|
|
* @returns {Promise<number>} Time for ping in ms rounded to nearest integer
|
|
|
|
*/
|
2021-08-10 06:03:14 -07:00
|
|
|
exports.ping = async (hostname) => {
|
|
|
|
try {
|
2021-08-10 07:00:29 -07:00
|
|
|
return await exports.pingAsync(hostname);
|
2021-08-10 06:03:14 -07:00
|
|
|
} catch (e) {
|
|
|
|
// If the host cannot be resolved, try again with ipv6
|
|
|
|
if (e.message.includes("service not known")) {
|
2021-08-10 07:00:29 -07:00
|
|
|
return await exports.pingAsync(hostname, true);
|
2021-08-10 06:03:14 -07:00
|
|
|
} else {
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
2021-09-20 01:22:18 -07:00
|
|
|
};
|
2021-08-10 06:03:14 -07:00
|
|
|
|
2022-04-20 11:56:40 -07:00
|
|
|
/**
|
|
|
|
* Ping the specified machine
|
|
|
|
* @param {string} hostname Hostname / address of machine to ping
|
|
|
|
* @param {boolean} ipv6 Should IPv6 be used?
|
|
|
|
* @returns {Promise<number>} Time for ping in ms rounded to nearest integer
|
|
|
|
*/
|
2021-08-10 06:03:14 -07:00
|
|
|
exports.pingAsync = function (hostname, ipv6 = false) {
|
2021-07-01 02:00:23 -07:00
|
|
|
return new Promise((resolve, reject) => {
|
2021-08-10 06:03:14 -07:00
|
|
|
const ping = new Ping(hostname, {
|
|
|
|
ipv6
|
|
|
|
});
|
2021-07-01 02:00:23 -07:00
|
|
|
|
2021-08-10 06:03:14 -07:00
|
|
|
ping.send(function (err, ms, stdout) {
|
2021-07-01 02:00:23 -07:00
|
|
|
if (err) {
|
2021-08-10 06:03:14 -07:00
|
|
|
reject(err);
|
2021-07-01 02:00:23 -07:00
|
|
|
} else if (ms === null) {
|
2021-09-20 01:22:18 -07:00
|
|
|
reject(new Error(stdout));
|
2021-07-01 02:00:23 -07:00
|
|
|
} else {
|
2021-09-20 01:22:18 -07:00
|
|
|
resolve(Math.round(ms));
|
2021-07-01 02:00:23 -07:00
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
2021-09-20 01:22:18 -07:00
|
|
|
};
|
2021-07-08 23:14:03 -07:00
|
|
|
|
2022-04-21 05:01:22 -07:00
|
|
|
/**
|
|
|
|
* MQTT Monitor
|
2022-04-21 10:53:07 -07:00
|
|
|
* @param {string} hostname Hostname / address of machine to test
|
|
|
|
* @param {string} topic MQTT topic
|
|
|
|
* @param {string} okMessage Expected result
|
|
|
|
* @param {Object} [options={}] MQTT options. Contains port, username,
|
|
|
|
* password and interval (interval defaults to 20)
|
|
|
|
* @returns {Promise<string>}
|
2022-04-21 05:01:22 -07:00
|
|
|
*/
|
2021-11-22 00:21:53 -08:00
|
|
|
exports.mqttAsync = function (hostname, topic, okMessage, options = {}) {
|
2021-11-03 18:46:43 -07:00
|
|
|
return new Promise((resolve, reject) => {
|
2021-11-22 00:21:53 -08:00
|
|
|
const { port, username, password, interval = 20 } = options;
|
2022-01-12 20:42:34 -08:00
|
|
|
|
|
|
|
// Adds MQTT protocol to the hostname if not already present
|
|
|
|
if (!/^(?:http|mqtt)s?:\/\//.test(hostname)) {
|
|
|
|
hostname = "mqtt://" + hostname;
|
2021-11-03 18:46:43 -07:00
|
|
|
}
|
2022-01-12 20:42:34 -08:00
|
|
|
|
2022-01-20 10:20:54 -08:00
|
|
|
const timeoutID = setTimeout(() => {
|
2022-04-15 22:37:17 -07:00
|
|
|
log.debug("mqtt", "MQTT timeout triggered");
|
2022-01-20 10:20:54 -08:00
|
|
|
client.end();
|
2022-04-16 10:06:47 -07:00
|
|
|
reject(new Error("Timeout"));
|
2022-04-16 00:01:53 -07:00
|
|
|
}, interval * 1000 * 0.8);
|
2022-01-20 10:20:54 -08:00
|
|
|
|
2022-04-15 22:37:17 -07:00
|
|
|
log.debug("mqtt", "MQTT connecting");
|
2022-01-12 20:42:34 -08:00
|
|
|
|
|
|
|
let client = mqtt.connect(hostname, {
|
|
|
|
port,
|
|
|
|
username,
|
|
|
|
password
|
|
|
|
});
|
|
|
|
|
|
|
|
client.on("connect", () => {
|
2022-04-16 10:06:47 -07:00
|
|
|
log.debug("mqtt", "MQTT connected");
|
2022-04-15 23:50:48 -07:00
|
|
|
|
|
|
|
try {
|
2022-04-16 10:06:47 -07:00
|
|
|
log.debug("mqtt", "MQTT subscribe topic");
|
2022-04-15 23:50:48 -07:00
|
|
|
client.subscribe(topic);
|
|
|
|
} catch (e) {
|
2022-04-16 00:01:53 -07:00
|
|
|
client.end();
|
|
|
|
clearTimeout(timeoutID);
|
2022-04-15 23:50:48 -07:00
|
|
|
reject(new Error("Cannot subscribe topic"));
|
|
|
|
}
|
2022-01-12 20:42:34 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
client.on("error", (error) => {
|
|
|
|
client.end();
|
2022-01-20 10:20:54 -08:00
|
|
|
clearTimeout(timeoutID);
|
2022-01-12 20:42:34 -08:00
|
|
|
reject(error);
|
|
|
|
});
|
|
|
|
|
|
|
|
client.on("message", (messageTopic, message) => {
|
2022-04-25 16:26:57 -07:00
|
|
|
if (messageTopic === topic) {
|
2022-01-20 10:20:54 -08:00
|
|
|
client.end();
|
|
|
|
clearTimeout(timeoutID);
|
2022-04-17 22:04:55 -07:00
|
|
|
if (okMessage != null && okMessage !== "" && message.toString() !== okMessage) {
|
|
|
|
reject(new Error(`Message Mismatch - Topic: ${messageTopic}; Message: ${message.toString()}`));
|
2022-01-12 20:42:34 -08:00
|
|
|
} else {
|
2022-04-17 22:04:55 -07:00
|
|
|
resolve(`Topic: ${messageTopic}; Message: ${message.toString()}`);
|
2022-01-12 20:42:34 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2021-11-03 18:46:43 -07:00
|
|
|
});
|
2022-05-13 10:58:23 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Use NTLM Auth for a http request.
|
|
|
|
* @param {Object} options The http request options
|
|
|
|
* @param {Object} ntlmOptions The auth options
|
|
|
|
* @returns {Promise<(string[]|Object[]|Object)>}
|
|
|
|
*/
|
|
|
|
exports.httpNtlm = function (options, ntlmOptions) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
let client = NtlmClient(ntlmOptions);
|
|
|
|
|
|
|
|
client(options)
|
|
|
|
.then((resp) => {
|
|
|
|
resolve(resp);
|
|
|
|
})
|
|
|
|
.catch((err) => {
|
|
|
|
reject(err);
|
|
|
|
});
|
|
|
|
});
|
2021-11-03 18:46:43 -07:00
|
|
|
};
|
|
|
|
|
2022-04-20 11:56:40 -07:00
|
|
|
/**
|
|
|
|
* Resolves a given record using the specified DNS server
|
|
|
|
* @param {string} hostname The hostname of the record to lookup
|
2022-04-21 05:01:22 -07:00
|
|
|
* @param {string} resolverServer The DNS server to use
|
2022-04-27 10:26:11 -07:00
|
|
|
* @param {string} resolverPort Port the DNS server is listening on
|
2022-04-20 11:56:40 -07:00
|
|
|
* @param {string} rrtype The type of record to request
|
|
|
|
* @returns {Promise<(string[]|Object[]|Object)>}
|
|
|
|
*/
|
2022-04-23 17:06:45 -07:00
|
|
|
exports.dnsResolve = function (hostname, resolverServer, resolverPort, rrtype) {
|
2021-08-22 15:05:48 -07:00
|
|
|
const resolver = new Resolver();
|
2022-04-15 11:59:32 -07:00
|
|
|
// Remove brackets from IPv6 addresses so we can re-add them to
|
|
|
|
// prevent issues with ::1:5300 (::1 port 5300)
|
2022-04-23 17:06:45 -07:00
|
|
|
resolverServer = resolverServer.replace("[", "").replace("]", "");
|
2022-05-31 22:05:12 -07:00
|
|
|
resolver.setServers([ `[${resolverServer}]:${resolverPort}` ]);
|
2021-08-22 15:05:48 -07:00
|
|
|
return new Promise((resolve, reject) => {
|
2022-04-17 00:43:03 -07:00
|
|
|
if (rrtype === "PTR") {
|
2021-08-22 15:05:48 -07:00
|
|
|
resolver.reverse(hostname, (err, records) => {
|
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
} else {
|
|
|
|
resolve(records);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
resolver.resolve(hostname, rrtype, (err, records) => {
|
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
} else {
|
|
|
|
resolve(records);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2021-09-20 01:22:18 -07:00
|
|
|
});
|
|
|
|
};
|
2021-08-22 15:05:48 -07:00
|
|
|
|
2022-05-12 10:48:03 -07:00
|
|
|
/**
|
|
|
|
* Run a query on SQL Server
|
|
|
|
* @param {string} connectionString The database connection string
|
|
|
|
* @param {string} query The query to validate the database with
|
|
|
|
* @returns {Promise<(string[]|Object[]|Object)>}
|
|
|
|
*/
|
2022-05-13 06:57:06 -07:00
|
|
|
exports.mssqlQuery = function (connectionString, query) {
|
2022-05-12 10:48:03 -07:00
|
|
|
return new Promise((resolve, reject) => {
|
2022-05-13 06:40:46 -07:00
|
|
|
mssql.connect(connectionString).then(pool => {
|
2022-05-12 10:48:03 -07:00
|
|
|
return pool.request()
|
|
|
|
.query(query);
|
|
|
|
}).then(result => {
|
|
|
|
resolve(result);
|
|
|
|
}).catch(err => {
|
|
|
|
reject(err);
|
2022-05-13 07:34:31 -07:00
|
|
|
}).finally(() => {
|
|
|
|
mssql.close();
|
2022-05-12 10:48:03 -07:00
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2022-06-15 10:12:47 -07:00
|
|
|
/**
|
|
|
|
* Run a query on Postgres
|
|
|
|
* @param {string} connectionString The database connection string
|
|
|
|
* @param {string} query The query to validate the database with
|
|
|
|
* @returns {Promise<(string[]|Object[]|Object)>}
|
|
|
|
*/
|
|
|
|
exports.postgresQuery = function (connectionString, query) {
|
|
|
|
return new Promise((resolve, reject) => {
|
2022-06-15 11:00:14 -07:00
|
|
|
const config = postgresConParse(connectionString);
|
|
|
|
|
|
|
|
if (config.password === "") {
|
|
|
|
// See https://github.com/brianc/node-postgres/issues/1927
|
|
|
|
return reject(new Error("Password is undefined."));
|
|
|
|
}
|
|
|
|
|
2022-06-15 10:12:47 -07:00
|
|
|
const client = new Client({ connectionString });
|
|
|
|
|
|
|
|
client.connect();
|
|
|
|
|
2022-06-15 11:00:14 -07:00
|
|
|
return client.query(query)
|
2022-06-15 10:12:47 -07:00
|
|
|
.then(res => {
|
|
|
|
resolve(res);
|
|
|
|
})
|
|
|
|
.catch(err => {
|
|
|
|
reject(err);
|
|
|
|
})
|
|
|
|
.finally(() => {
|
|
|
|
client.end();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2022-10-12 09:32:05 -07:00
|
|
|
/**
|
|
|
|
* Query radius server
|
|
|
|
* @param {string} hostname Hostname of radius server
|
|
|
|
* @param {string} username Username to use
|
|
|
|
* @param {string} password Password to use
|
|
|
|
* @param {string} calledStationId ID of called station
|
|
|
|
* @param {string} callingStationId ID of calling station
|
|
|
|
* @param {string} secret Secret to use
|
|
|
|
* @param {number} [port=1812] Port to contact radius server on
|
|
|
|
* @returns {Promise<any>}
|
|
|
|
*/
|
2022-05-12 02:48:38 -07:00
|
|
|
exports.radius = function (
|
|
|
|
hostname,
|
|
|
|
username,
|
|
|
|
password,
|
|
|
|
calledStationId,
|
|
|
|
callingStationId,
|
|
|
|
secret,
|
2022-10-12 09:32:05 -07:00
|
|
|
port = 1812,
|
2022-05-12 02:48:38 -07:00
|
|
|
) {
|
|
|
|
const client = new radiusClient({
|
|
|
|
host: hostname,
|
2022-10-12 09:32:05 -07:00
|
|
|
hostPort: port,
|
2022-05-12 02:48:38 -07:00
|
|
|
dictionaries: [ file ],
|
|
|
|
});
|
|
|
|
|
|
|
|
return client.accessRequest({
|
|
|
|
secret: secret,
|
|
|
|
attributes: [
|
|
|
|
[ attributes.USER_NAME, username ],
|
|
|
|
[ attributes.USER_PASSWORD, password ],
|
|
|
|
[ attributes.CALLING_STATION_ID, callingStationId ],
|
|
|
|
[ attributes.CALLED_STATION_ID, calledStationId ],
|
|
|
|
],
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2022-04-20 11:56:40 -07:00
|
|
|
/**
|
|
|
|
* Retrieve value of setting based on key
|
|
|
|
* @param {string} key Key of setting to retrieve
|
2022-05-05 23:41:34 -07:00
|
|
|
* @returns {Promise<any>} Value
|
2022-07-31 08:41:29 -07:00
|
|
|
* @deprecated Use await Settings.get(key)
|
2022-04-20 11:56:40 -07:00
|
|
|
*/
|
2021-07-08 23:14:03 -07:00
|
|
|
exports.setting = async function (key) {
|
2022-06-28 23:57:40 -07:00
|
|
|
return await Settings.get(key);
|
2021-09-20 01:22:18 -07:00
|
|
|
};
|
2021-07-08 23:14:03 -07:00
|
|
|
|
2022-04-20 11:56:40 -07:00
|
|
|
/**
|
|
|
|
* Sets the specified setting to specifed value
|
|
|
|
* @param {string} key Key of setting to set
|
|
|
|
* @param {any} value Value to set to
|
|
|
|
* @param {?string} type Type of setting
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
2021-10-09 09:16:13 -07:00
|
|
|
exports.setSetting = async function (key, value, type = null) {
|
2022-06-28 23:57:40 -07:00
|
|
|
await Settings.set(key, value, type);
|
2021-09-20 01:22:18 -07:00
|
|
|
};
|
2021-07-21 11:02:35 -07:00
|
|
|
|
2022-04-20 11:56:40 -07:00
|
|
|
/**
|
|
|
|
* Get settings based on type
|
2022-06-28 23:57:40 -07:00
|
|
|
* @param {string} type The type of setting
|
2022-04-20 11:56:40 -07:00
|
|
|
* @returns {Promise<Bean>}
|
|
|
|
*/
|
2021-07-08 23:14:03 -07:00
|
|
|
exports.getSettings = async function (type) {
|
2022-06-28 23:57:40 -07:00
|
|
|
return await Settings.getSettings(type);
|
2021-09-20 01:22:18 -07:00
|
|
|
};
|
2021-07-20 21:09:09 -07:00
|
|
|
|
2022-04-20 11:56:40 -07:00
|
|
|
/**
|
|
|
|
* Set settings based on type
|
2022-06-28 23:57:40 -07:00
|
|
|
* @param {string} type Type of settings to set
|
2022-04-20 11:56:40 -07:00
|
|
|
* @param {Object} data Values of settings
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
2021-07-31 06:57:58 -07:00
|
|
|
exports.setSettings = async function (type, data) {
|
2022-06-28 23:57:40 -07:00
|
|
|
await Settings.setSettings(type, data);
|
2021-09-20 01:22:18 -07:00
|
|
|
};
|
2021-07-31 06:57:58 -07:00
|
|
|
|
2021-07-20 21:09:09 -07:00
|
|
|
// ssl-checker by @dyaa
|
2022-04-20 11:56:40 -07:00
|
|
|
//https://github.com/dyaa/ssl-checker/blob/master/src/index.ts
|
2021-07-20 21:09:09 -07:00
|
|
|
|
2022-04-20 11:56:40 -07:00
|
|
|
/**
|
|
|
|
* Get number of days between two dates
|
|
|
|
* @param {Date} validFrom Start date
|
|
|
|
* @param {Date} validTo End date
|
|
|
|
* @returns {number}
|
|
|
|
*/
|
2021-07-20 21:09:09 -07:00
|
|
|
const getDaysBetween = (validFrom, validTo) =>
|
|
|
|
Math.round(Math.abs(+validFrom - +validTo) / 8.64e7);
|
|
|
|
|
2022-04-20 11:56:40 -07:00
|
|
|
/**
|
|
|
|
* Get days remaining from a time range
|
|
|
|
* @param {Date} validFrom Start date
|
|
|
|
* @param {Date} validTo End date
|
|
|
|
* @returns {number}
|
|
|
|
*/
|
2021-07-20 21:09:09 -07:00
|
|
|
const getDaysRemaining = (validFrom, validTo) => {
|
|
|
|
const daysRemaining = getDaysBetween(validFrom, validTo);
|
|
|
|
if (new Date(validTo).getTime() < new Date().getTime()) {
|
|
|
|
return -daysRemaining;
|
|
|
|
}
|
|
|
|
return daysRemaining;
|
|
|
|
};
|
|
|
|
|
2022-04-20 11:56:40 -07:00
|
|
|
/**
|
|
|
|
* Fix certificate info for display
|
|
|
|
* @param {Object} info The chain obtained from getPeerCertificate()
|
|
|
|
* @returns {Object} An object representing certificate information
|
|
|
|
*/
|
2021-10-01 03:44:32 -07:00
|
|
|
const parseCertificateInfo = function (info) {
|
|
|
|
let link = info;
|
2021-11-07 23:39:17 -08:00
|
|
|
let i = 0;
|
|
|
|
|
|
|
|
const existingList = {};
|
2021-10-01 03:44:32 -07:00
|
|
|
|
|
|
|
while (link) {
|
2022-04-15 23:50:48 -07:00
|
|
|
log.debug("cert", `[${i}] ${link.fingerprint}`);
|
2021-11-07 23:39:17 -08:00
|
|
|
|
2021-10-01 03:44:32 -07:00
|
|
|
if (!link.valid_from || !link.valid_to) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
link.validTo = new Date(link.valid_to);
|
|
|
|
link.validFor = link.subjectaltname?.replace(/DNS:|IP Address:/g, "").split(", ");
|
|
|
|
link.daysRemaining = getDaysRemaining(new Date(), link.validTo);
|
|
|
|
|
2021-11-07 23:39:17 -08:00
|
|
|
existingList[link.fingerprint] = true;
|
|
|
|
|
2021-10-01 03:44:32 -07:00
|
|
|
// Move up the chain until loop is encountered
|
|
|
|
if (link.issuerCertificate == null) {
|
|
|
|
break;
|
2021-11-07 23:39:17 -08:00
|
|
|
} else if (link.issuerCertificate.fingerprint in existingList) {
|
2022-04-15 23:50:48 -07:00
|
|
|
log.debug("cert", `[Last] ${link.issuerCertificate.fingerprint}`);
|
2021-10-01 03:44:32 -07:00
|
|
|
link.issuerCertificate = null;
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
link = link.issuerCertificate;
|
|
|
|
}
|
2021-11-07 23:39:17 -08:00
|
|
|
|
|
|
|
// Should be no use, but just in case.
|
|
|
|
if (i > 500) {
|
|
|
|
throw new Error("Dead loop occurred in parseCertificateInfo");
|
|
|
|
}
|
|
|
|
i++;
|
2021-07-20 21:09:09 -07:00
|
|
|
}
|
|
|
|
|
2021-10-01 03:44:32 -07:00
|
|
|
return info;
|
|
|
|
};
|
2021-07-20 21:09:09 -07:00
|
|
|
|
2022-04-20 11:56:40 -07:00
|
|
|
/**
|
|
|
|
* Check if certificate is valid
|
|
|
|
* @param {Object} res Response object from axios
|
|
|
|
* @returns {Object} Object containing certificate information
|
|
|
|
*/
|
2021-10-01 03:44:32 -07:00
|
|
|
exports.checkCertificate = function (res) {
|
|
|
|
const info = res.request.res.socket.getPeerCertificate(true);
|
|
|
|
const valid = res.request.res.socket.authorized || false;
|
2021-07-20 21:09:09 -07:00
|
|
|
|
2022-04-15 23:50:48 -07:00
|
|
|
log.debug("cert", "Parsing Certificate Info");
|
2021-10-01 03:44:32 -07:00
|
|
|
const parsedInfo = parseCertificateInfo(info);
|
2021-07-20 21:09:09 -07:00
|
|
|
|
|
|
|
return {
|
2021-10-01 03:44:32 -07:00
|
|
|
valid: valid,
|
|
|
|
certInfo: parsedInfo
|
2021-07-20 21:09:09 -07:00
|
|
|
};
|
2021-09-20 01:22:18 -07:00
|
|
|
};
|
2021-08-05 04:04:38 -07:00
|
|
|
|
2022-04-20 11:56:40 -07:00
|
|
|
/**
|
|
|
|
* Check if the provided status code is within the accepted ranges
|
2022-07-18 07:06:25 -07:00
|
|
|
* @param {number} status The status code to check
|
2022-04-21 12:02:18 -07:00
|
|
|
* @param {string[]} acceptedCodes An array of accepted status codes
|
2022-04-20 11:56:40 -07:00
|
|
|
* @returns {boolean} True if status code within range, false otherwise
|
|
|
|
* @throws {Error} Will throw an error if the provided status code is not a valid range string or code string
|
|
|
|
*/
|
2022-04-16 10:39:49 -07:00
|
|
|
exports.checkStatusCode = function (status, acceptedCodes) {
|
|
|
|
if (acceptedCodes == null || acceptedCodes.length === 0) {
|
2021-08-05 04:04:38 -07:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-04-16 10:39:49 -07:00
|
|
|
for (const codeRange of acceptedCodes) {
|
|
|
|
const codeRangeSplit = codeRange.split("-").map(string => parseInt(string));
|
|
|
|
if (codeRangeSplit.length === 1) {
|
|
|
|
if (status === codeRangeSplit[0]) {
|
2021-08-05 04:04:38 -07:00
|
|
|
return true;
|
|
|
|
}
|
2022-04-16 10:39:49 -07:00
|
|
|
} else if (codeRangeSplit.length === 2) {
|
|
|
|
if (status >= codeRangeSplit[0] && status <= codeRangeSplit[1]) {
|
2021-08-05 04:04:38 -07:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw new Error("Invalid status code range");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2021-09-20 01:22:18 -07:00
|
|
|
};
|
2021-08-29 23:55:33 -07:00
|
|
|
|
2022-04-20 11:56:40 -07:00
|
|
|
/**
|
|
|
|
* Get total number of clients in room
|
|
|
|
* @param {Server} io Socket server instance
|
|
|
|
* @param {string} roomName Name of room to check
|
|
|
|
* @returns {number}
|
|
|
|
*/
|
2021-08-29 23:55:33 -07:00
|
|
|
exports.getTotalClientInRoom = (io, roomName) => {
|
|
|
|
|
|
|
|
const sockets = io.sockets;
|
|
|
|
|
2021-11-03 18:46:43 -07:00
|
|
|
if (!sockets) {
|
2021-08-29 23:55:33 -07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
const adapter = sockets.adapter;
|
|
|
|
|
2021-11-03 18:46:43 -07:00
|
|
|
if (!adapter) {
|
2021-08-29 23:55:33 -07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
const room = adapter.rooms.get(roomName);
|
|
|
|
|
|
|
|
if (room) {
|
|
|
|
return room.size;
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
2021-09-20 01:22:18 -07:00
|
|
|
};
|
2021-09-11 04:40:03 -07:00
|
|
|
|
2022-04-20 11:56:40 -07:00
|
|
|
/**
|
|
|
|
* Allow CORS all origins if development
|
|
|
|
* @param {Object} res Response object from axios
|
|
|
|
*/
|
2021-09-11 04:40:03 -07:00
|
|
|
exports.allowDevAllOrigin = (res) => {
|
|
|
|
if (process.env.NODE_ENV === "development") {
|
|
|
|
exports.allowAllOrigin(res);
|
|
|
|
}
|
2021-09-20 01:22:18 -07:00
|
|
|
};
|
2021-09-11 04:40:03 -07:00
|
|
|
|
2022-04-20 11:56:40 -07:00
|
|
|
/**
|
|
|
|
* Allow CORS all origins
|
|
|
|
* @param {Object} res Response object from axios
|
|
|
|
*/
|
2021-09-11 04:40:03 -07:00
|
|
|
exports.allowAllOrigin = (res) => {
|
|
|
|
res.header("Access-Control-Allow-Origin", "*");
|
|
|
|
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
2021-09-20 01:22:18 -07:00
|
|
|
};
|
2021-09-16 07:48:28 -07:00
|
|
|
|
2022-04-20 11:56:40 -07:00
|
|
|
/**
|
|
|
|
* Check if a user is logged in
|
|
|
|
* @param {Socket} socket Socket instance
|
|
|
|
*/
|
2021-09-16 07:48:28 -07:00
|
|
|
exports.checkLogin = (socket) => {
|
2021-11-03 18:46:43 -07:00
|
|
|
if (!socket.userID) {
|
2021-09-16 07:48:28 -07:00
|
|
|
throw new Error("You are not logged in.");
|
|
|
|
}
|
2021-09-20 01:22:18 -07:00
|
|
|
};
|
2021-10-05 04:13:57 -07:00
|
|
|
|
2022-03-29 02:38:48 -07:00
|
|
|
/**
|
|
|
|
* For logged-in users, double-check the password
|
2022-04-21 05:01:22 -07:00
|
|
|
* @param {Socket} socket Socket.io instance
|
|
|
|
* @param {string} currentPassword
|
2022-03-29 02:38:48 -07:00
|
|
|
* @returns {Promise<Bean>}
|
|
|
|
*/
|
|
|
|
exports.doubleCheckPassword = async (socket, currentPassword) => {
|
|
|
|
if (typeof currentPassword !== "string") {
|
|
|
|
throw new Error("Wrong data type?");
|
|
|
|
}
|
|
|
|
|
|
|
|
let user = await R.findOne("user", " id = ? AND active = 1 ", [
|
|
|
|
socket.userID,
|
|
|
|
]);
|
|
|
|
|
|
|
|
if (!user || !passwordHash.verify(currentPassword, user.password)) {
|
|
|
|
throw new Error("Incorrect current password");
|
|
|
|
}
|
|
|
|
|
|
|
|
return user;
|
|
|
|
};
|
|
|
|
|
2022-04-20 11:56:40 -07:00
|
|
|
/** Start Unit tests */
|
2021-10-05 04:13:57 -07:00
|
|
|
exports.startUnitTest = async () => {
|
|
|
|
console.log("Starting unit test...");
|
|
|
|
const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm";
|
2022-10-04 23:26:30 -07:00
|
|
|
const child = childProcess.spawn(npm, [ "run", "jest-backend" ]);
|
2021-10-05 04:13:57 -07:00
|
|
|
|
|
|
|
child.stdout.on("data", (data) => {
|
|
|
|
console.log(data.toString());
|
|
|
|
});
|
|
|
|
|
|
|
|
child.stderr.on("data", (data) => {
|
|
|
|
console.log(data.toString());
|
|
|
|
});
|
|
|
|
|
|
|
|
child.on("close", function (code) {
|
|
|
|
console.log("Jest exit code: " + code);
|
2021-10-05 05:40:40 -07:00
|
|
|
process.exit(code);
|
2021-10-05 04:13:57 -07:00
|
|
|
});
|
|
|
|
};
|
2021-10-13 09:22:49 -07:00
|
|
|
|
2022-06-16 05:40:42 -07:00
|
|
|
/** Start end-to-end tests */
|
2022-06-16 02:28:17 -07:00
|
|
|
exports.startE2eTests = async () => {
|
|
|
|
console.log("Starting unit test...");
|
|
|
|
const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm";
|
|
|
|
const child = childProcess.spawn(npm, [ "run", "cy:run" ]);
|
|
|
|
|
|
|
|
child.stdout.on("data", (data) => {
|
|
|
|
console.log(data.toString());
|
|
|
|
});
|
|
|
|
|
|
|
|
child.stderr.on("data", (data) => {
|
|
|
|
console.log(data.toString());
|
|
|
|
});
|
|
|
|
|
|
|
|
child.on("close", function (code) {
|
|
|
|
console.log("Jest exit code: " + code);
|
|
|
|
process.exit(code);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2021-10-13 09:22:49 -07:00
|
|
|
/**
|
2022-04-20 11:56:40 -07:00
|
|
|
* Convert unknown string to UTF8
|
|
|
|
* @param {Uint8Array} body Buffer
|
2021-10-13 09:22:49 -07:00
|
|
|
* @returns {string}
|
|
|
|
*/
|
|
|
|
exports.convertToUTF8 = (body) => {
|
|
|
|
const guessEncoding = chardet.detect(body);
|
|
|
|
const str = iconv.decode(body, guessEncoding);
|
|
|
|
return str.toString();
|
|
|
|
};
|
2021-10-29 03:24:47 -07:00
|
|
|
|
2022-01-03 07:04:37 -08:00
|
|
|
/**
|
|
|
|
* Returns a color code in hex format based on a given percentage:
|
|
|
|
* 0% => hue = 10 => red
|
|
|
|
* 100% => hue = 90 => green
|
|
|
|
*
|
2022-04-30 06:36:00 -07:00
|
|
|
* @param {number} percentage float, 0 to 1
|
2022-04-30 06:36:07 -07:00
|
|
|
* @param {number} maxHue
|
2022-01-03 07:04:37 -08:00
|
|
|
* @param {number} minHue, int
|
|
|
|
* @returns {string}, hex value
|
|
|
|
*/
|
2022-01-03 06:48:52 -08:00
|
|
|
exports.percentageToColor = (percentage, maxHue = 90, minHue = 10) => {
|
|
|
|
const hue = percentage * (maxHue - minHue) + minHue;
|
|
|
|
try {
|
|
|
|
return chroma(`hsl(${hue}, 90%, 40%)`).hex();
|
|
|
|
} catch (err) {
|
2022-01-04 03:21:53 -08:00
|
|
|
return badgeConstants.naColor;
|
2022-01-03 06:48:52 -08:00
|
|
|
}
|
|
|
|
};
|
2022-01-04 07:00:21 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Joins and array of string to one string after filtering out empty values
|
|
|
|
*
|
|
|
|
* @param {string[]} parts
|
|
|
|
* @param {string} connector
|
|
|
|
* @returns {string}
|
|
|
|
*/
|
|
|
|
exports.filterAndJoin = (parts, connector = "") => {
|
|
|
|
return parts.filter((part) => !!part && part !== "").join(connector);
|
|
|
|
};
|
2022-05-31 22:05:12 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Send a 403 response
|
|
|
|
* @param {Object} res Express response object
|
|
|
|
* @param {string} [msg=""] Message to send
|
|
|
|
*/
|
|
|
|
module.exports.send403 = (res, msg = "") => {
|
|
|
|
res.status(403).json({
|
|
|
|
"status": "fail",
|
|
|
|
"msg": msg,
|
|
|
|
});
|
|
|
|
};
|
2022-09-25 04:38:28 -07:00
|
|
|
|
|
|
|
function timeObjectConvertTimezone(obj, timezone, timeObjectToUTC = true) {
|
2022-10-10 05:48:11 -07:00
|
|
|
let offsetString;
|
|
|
|
|
|
|
|
if (timezone) {
|
|
|
|
offsetString = dayjs().tz(timezone).format("Z");
|
|
|
|
} else {
|
|
|
|
offsetString = dayjs().format("Z");
|
|
|
|
}
|
|
|
|
|
2022-09-25 04:38:28 -07:00
|
|
|
let hours = parseInt(offsetString.substring(1, 3));
|
|
|
|
let minutes = parseInt(offsetString.substring(4, 6));
|
|
|
|
|
|
|
|
if (
|
|
|
|
(timeObjectToUTC && offsetString.startsWith("+")) ||
|
|
|
|
(!timeObjectToUTC && offsetString.startsWith("-"))
|
|
|
|
) {
|
|
|
|
hours *= -1;
|
|
|
|
minutes *= -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
obj.hours += hours;
|
|
|
|
obj.minutes += minutes;
|
|
|
|
|
|
|
|
// Handle out of bound
|
2022-10-12 02:02:16 -07:00
|
|
|
if (obj.minutes < 0) {
|
|
|
|
obj.minutes += 60;
|
|
|
|
obj.hours--;
|
|
|
|
} else if (obj.minutes > 60) {
|
|
|
|
obj.minutes -= 60;
|
|
|
|
obj.hours++;
|
|
|
|
}
|
|
|
|
|
2022-09-25 04:38:28 -07:00
|
|
|
if (obj.hours < 0) {
|
|
|
|
obj.hours += 24;
|
|
|
|
} else if (obj.hours > 24) {
|
|
|
|
obj.hours -= 24;
|
|
|
|
}
|
|
|
|
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
2022-10-10 05:48:11 -07:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {object} obj
|
|
|
|
* @param {string} timezone
|
|
|
|
* @returns {object}
|
|
|
|
*/
|
|
|
|
module.exports.timeObjectToUTC = (obj, timezone = undefined) => {
|
2022-09-25 04:38:28 -07:00
|
|
|
return timeObjectConvertTimezone(obj, timezone, true);
|
|
|
|
};
|
|
|
|
|
2022-10-10 05:48:11 -07:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {object} obj
|
|
|
|
* @param {string} timezone
|
|
|
|
* @returns {object}
|
|
|
|
*/
|
|
|
|
module.exports.timeObjectToLocal = (obj, timezone = undefined) => {
|
2022-09-25 04:38:28 -07:00
|
|
|
return timeObjectConvertTimezone(obj, timezone, false);
|
|
|
|
};
|