thelounge/src/helper.js

342 lines
7.4 KiB
JavaScript
Raw Normal View History

"use strict";
const pkg = require("../package.json");
2018-01-11 11:33:36 +00:00
const _ = require("lodash");
2018-06-15 20:31:06 +00:00
const log = require("./log");
2018-01-11 11:33:36 +00:00
const path = require("path");
const os = require("os");
const fs = require("fs");
const net = require("net");
const bcrypt = require("bcryptjs");
2018-03-02 18:28:54 +00:00
const colors = require("chalk");
const crypto = require("crypto");
2014-10-04 23:22:23 +00:00
let homePath;
let configPath;
let usersPath;
let storagePath;
let packagesPath;
let fileUploadPath;
let userLogsPath;
const Helper = {
config: null,
expandHome,
getHomePath,
getPackagesPath,
getPackageModulePath,
getStoragePath,
getConfigPath,
getFileUploadPath,
getUsersPath,
getUserConfigPath,
getUserLogsPath,
setHome,
getVersion,
getVersionCacheBust,
getGitCommit,
ip2hex,
mergeConfig,
getDefaultNick,
parseHostmask,
compareHostmask,
2016-10-21 19:00:43 +00:00
password: {
hash: passwordHash,
compare: passwordCompare,
requiresUpdate: passwordRequiresUpdate,
},
};
2016-05-09 16:19:16 +00:00
module.exports = Helper;
2019-07-17 09:33:59 +00:00
Helper.config = require(path.resolve(path.join(__dirname, "..", "defaults", "config.js")));
function getVersion() {
const gitCommit = getGitCommit();
const version = `v${pkg.version}`;
return gitCommit ? `source (${gitCommit} / ${version})` : version;
}
let _gitCommit;
function getGitCommit() {
if (_gitCommit !== undefined) {
return _gitCommit;
}
if (!fs.existsSync(path.resolve(__dirname, "..", ".git", "HEAD"))) {
_gitCommit = null;
return null;
}
try {
_gitCommit = require("child_process")
.execSync(
"git rev-parse --short HEAD", // Returns hash of current commit
{stdio: ["ignore", "pipe", "ignore"]}
)
.toString()
.trim();
return _gitCommit;
} catch (e) {
// Not a git repository or git is not installed
_gitCommit = null;
return null;
}
}
function getVersionCacheBust() {
2019-07-17 09:33:59 +00:00
const hash = crypto
.createHash("sha256")
.update(Helper.getVersion())
.digest("hex");
return hash.substring(0, 10);
}
function setHome(newPath) {
homePath = expandHome(newPath);
configPath = path.join(homePath, "config.js");
usersPath = path.join(homePath, "users");
storagePath = path.join(homePath, "storage");
fileUploadPath = path.join(homePath, "uploads");
packagesPath = path.join(homePath, "packages");
userLogsPath = path.join(homePath, "logs");
// Reload config from new home location
if (fs.existsSync(configPath)) {
const userConfig = require(configPath);
if (_.isEmpty(userConfig)) {
2019-07-17 09:33:59 +00:00
log.warn(
`The file located at ${colors.green(
configPath
)} does not appear to expose anything.`
);
log.warn(
`Make sure it is non-empty and the configuration is exported using ${colors.bold(
"module.exports = { ... }"
)}.`
);
log.warn("Using default configuration...");
}
mergeConfig(this.config, userConfig);
2016-07-04 20:15:30 +00:00
}
2016-12-10 08:53:06 +00:00
if (!this.config.displayNetwork && !this.config.lockNetwork) {
this.config.lockNetwork = true;
2019-07-17 09:33:59 +00:00
log.warn(
`${colors.bold("displayNetwork")} and ${colors.bold(
"lockNetwork"
)} are false, setting ${colors.bold("lockNetwork")} to true.`
);
}
2019-07-17 09:33:59 +00:00
const manifestPath = path.resolve(
path.join(__dirname, "..", "public", "thelounge.webmanifest")
);
// Check if manifest exists, if not, the app most likely was not built
if (!fs.existsSync(manifestPath)) {
2019-07-17 09:33:59 +00:00
log.error(
`The client application was not built. Run ${colors.bold(
"NODE_ENV=production yarn build"
)} to resolve this.`
);
process.exit(1);
}
// Load theme color from the web manifest
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
this.config.themeColor = manifest.theme_color;
// TODO: Remove in future release
if (["example", "crypto", "zenburn"].includes(this.config.theme)) {
if (this.config.theme === "example") {
2019-07-17 09:33:59 +00:00
log.warn(
`The default theme ${colors.red("example")} was renamed to ${colors.green(
"default"
)} as of The Lounge v3.`
);
} else {
2019-07-17 09:33:59 +00:00
log.warn(
`The theme ${colors.red(
this.config.theme
)} was moved to a separate theme as of The Lounge v3.`
);
log.warn(
`Install it with ${colors.bold(
"thelounge install thelounge-theme-" + this.config.theme
)}.`
);
}
2019-07-17 09:33:59 +00:00
log.warn(
`Falling back to theme ${colors.green("default")} will be removed in a future release.`
);
log.warn("Please update your configuration file accordingly.");
this.config.theme = "default";
}
2016-05-09 16:19:16 +00:00
}
function getHomePath() {
return homePath;
}
function getConfigPath() {
return configPath;
}
function getFileUploadPath() {
return fileUploadPath;
}
function getUsersPath() {
return usersPath;
}
function getUserConfigPath(name) {
return path.join(usersPath, name + ".json");
}
function getUserLogsPath() {
return userLogsPath;
2016-05-15 21:13:51 +00:00
}
function getStoragePath() {
return storagePath;
}
2017-06-22 21:09:55 +00:00
function getPackagesPath() {
return packagesPath;
2017-06-22 21:09:55 +00:00
}
function getPackageModulePath(packageName) {
return path.join(Helper.getPackagesPath(), "node_modules", packageName);
2017-06-22 21:09:55 +00:00
}
2016-11-19 18:32:47 +00:00
function ip2hex(address) {
// no ipv6 support
if (!net.isIPv4(address)) {
return "00000000";
}
2019-07-17 09:33:59 +00:00
return address
.split(".")
.map(function(octet) {
let hex = parseInt(octet, 10).toString(16);
2016-11-19 18:32:47 +00:00
2019-07-17 09:33:59 +00:00
if (hex.length === 1) {
hex = "0" + hex;
}
2016-11-19 18:32:47 +00:00
2019-07-17 09:33:59 +00:00
return hex;
})
.join("");
2016-11-19 18:32:47 +00:00
}
// Expand ~ into the current user home dir.
// This does *not* support `~other_user/tmp` => `/home/other_user/tmp`.
function expandHome(shortenedPath) {
if (!shortenedPath) {
return "";
}
2017-08-16 07:06:20 +00:00
const home = os.homedir().replace("$", "$$$$");
return path.resolve(shortenedPath.replace(/^~($|\/|\\)/, home + "$1"));
}
2016-10-21 19:00:43 +00:00
function passwordRequiresUpdate(password) {
return bcrypt.getRounds(password) !== 11;
}
function passwordHash(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(11));
}
function passwordCompare(password, expected) {
return bcrypt.compare(password, expected);
2016-10-21 19:00:43 +00:00
}
function getDefaultNick() {
if (!this.config.defaults.nick) {
return "thelounge";
}
return this.config.defaults.nick.replace(/%/g, () => Math.floor(Math.random() * 10));
}
function mergeConfig(oldConfig, newConfig) {
for (const key in newConfig) {
if (!Object.prototype.hasOwnProperty.call(oldConfig, key)) {
log.warn(`Unknown key "${colors.bold(key)}", please verify your config.`);
}
}
return _.mergeWith(oldConfig, newConfig, (objValue, srcValue, key) => {
// Do not override config variables if the type is incorrect (e.g. object changed into a string)
2019-07-17 09:33:59 +00:00
if (
typeof objValue !== "undefined" &&
objValue !== null &&
typeof objValue !== typeof srcValue
) {
log.warn(`Incorrect type for "${colors.bold(key)}", please verify your config.`);
return objValue;
}
// For arrays, simply override the value with user provided one.
if (_.isArray(objValue)) {
return srcValue;
}
});
}
function parseHostmask(hostmask) {
let nick = "";
let ident = "*";
let hostname = "*";
let parts = [];
// Parse hostname first, then parse the rest
parts = hostmask.split("@");
if (parts.length >= 2) {
hostname = parts[1] || "*";
hostmask = parts[0];
}
hostname = hostname.toLowerCase();
parts = hostmask.split("!");
if (parts.length >= 2) {
ident = parts[1] || "*";
hostmask = parts[0];
}
ident = ident.toLowerCase();
nick = hostmask.toLowerCase() || "*";
const result = {
nick: nick,
ident: ident,
hostname: hostname,
};
return result;
}
function compareHostmask(a, b) {
2019-07-17 09:33:59 +00:00
return (
(a.nick.toLowerCase() === b.nick.toLowerCase() || a.nick === "*") &&
(a.ident.toLowerCase() === b.ident.toLowerCase() || a.ident === "*") &&
(a.hostname.toLowerCase() === b.hostname.toLowerCase() || a.hostname === "*")
);
}