Improved meshcentral-web customization support.

Ylian Saint-Hilaire committed Aug 25, 2019 at 11:08 UTC 0a60656e8de9bb9d32f541a556ae3a2dc333b2ee
2 files changed +76 -60
meshcentral.js
+8 -4
@@ -73,16 +73,20 @@ function CreateMeshCentralServer(config, args) {
73 obj.filespath = obj.path.join(__dirname, '../../meshcentral-files');
74 obj.backuppath = obj.path.join(__dirname, '../../meshcentral-backup');
75 obj.recordpath = obj.path.join(__dirname, '../../meshcentral-recordings');
76 - if (obj.fs.existsSync(obj.path.join(__dirname, '../../meshcentral-web/views'))) { obj.webViewsPath = obj.path.join(__dirname, '../../meshcentral-web/views'); } else { obj.webViewsPath = obj.path.join(__dirname, 'views'); }
77 - if (obj.fs.existsSync(obj.path.join(__dirname, '../../meshcentral-web/public'))) { obj.webPublicPath = obj.path.join(__dirname, '../../meshcentral-web/public'); } else { obj.webPublicPath = obj.path.join(__dirname, 'public'); }
76 + obj.webPublicPath = obj.path.join(__dirname, 'public');
77 + obj.webViewsPath = obj.path.join(__dirname, 'views');
78 + if (obj.fs.existsSync(obj.path.join(__dirname, '../../meshcentral-web/views'))) { obj.webViewsOverridePath = obj.path.join(__dirname, '../../meshcentral-web/views'); }
79 + if (obj.fs.existsSync(obj.path.join(__dirname, '../../meshcentral-web/public'))) { obj.webPublicOverridePath = obj.path.join(__dirname, '../../meshcentral-web/public'); }
80 } else {
81 obj.parentpath = __dirname;
82 obj.datapath = obj.path.join(__dirname, '../meshcentral-data');
83 obj.filespath = obj.path.join(__dirname, '../meshcentral-files');
84 obj.backuppath = obj.path.join(__dirname, '../meshcentral-backups');
85 obj.recordpath = obj.path.join(__dirname, '../meshcentral-recordings');
84 - if (obj.fs.existsSync(obj.path.join(__dirname, '../meshcentral-web/views'))) { obj.webViewsPath = obj.path.join(__dirname, '../meshcentral-web/views'); } else { obj.webViewsPath = obj.path.join(__dirname, 'views'); }
85 - if (obj.fs.existsSync(obj.path.join(__dirname, '../meshcentral-web/public'))) { obj.webPublicPath = obj.path.join(__dirname, '../meshcentral-web/public'); } else { obj.webPublicPath = obj.path.join(__dirname, 'public'); }
86 + obj.webPublicPath = obj.path.join(__dirname, 'public');
87 + obj.webViewsPath = obj.path.join(__dirname, 'views');
88 + if (obj.fs.existsSync(obj.path.join(__dirname, '../meshcentral-web/views'))) { obj.webViewsOverridePath = obj.path.join(__dirname, '../meshcentral-web/views'); }
89 + if (obj.fs.existsSync(obj.path.join(__dirname, '../meshcentral-web/public'))) { obj.webPublicOverridePath = obj.path.join(__dirname, '../meshcentral-web/public'); }
90 }
91
92 // Look to see if data and/or file path is specified
webserver.js
+68 -56
@@ -1066,28 +1066,28 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1066 var idsplit = cookie.u.split('/');
1067 if ((idsplit.length != 2) || (idsplit[0] != domain.id)) {
1068 parent.debug('web', 'handleCheckMailRequest: Invalid domain.');
1069 - res.render(obj.path.join(obj.parent.webViewsPath, 'message'), { title: domain.title, title2: domain.title2, title3: 'Account Verification', domainurl: domain.url, message: 'ERROR: Invalid domain. <a href="' + domain.url + '">Go to login page</a>.' });
1069 + res.render(getRenderPage('message', req), { title: domain.title, title2: domain.title2, title3: 'Account Verification', domainurl: domain.url, message: 'ERROR: Invalid domain. <a href="' + domain.url + '">Go to login page</a>.' });
1070 } else {
1071 obj.db.Get('user/' + cookie.u.toLowerCase(), function (err, docs) {
1072 if (docs.length == 0) {
1073 parent.debug('web', 'handleCheckMailRequest: Invalid username.');
1074 - res.render(obj.path.join(obj.parent.webViewsPath, 'message'), { title: domain.title, title2: domain.title2, title3: 'Account Verification', domainurl: domain.url, message: 'ERROR: Invalid username \"' + EscapeHtml(idsplit[1]) + '\". <a href="' + domain.url + '">Go to login page</a>.' });
1074 + res.render(getRenderPage('message', req), { title: domain.title, title2: domain.title2, title3: 'Account Verification', domainurl: domain.url, message: 'ERROR: Invalid username \"' + EscapeHtml(idsplit[1]) + '\". <a href="' + domain.url + '">Go to login page</a>.' });
1075 } else {
1076 var user = docs[0];
1077 if (user.email != cookie.e) {
1078 parent.debug('web', 'handleCheckMailRequest: Invalid e-mail.');
1079 - res.render(obj.path.join(obj.parent.webViewsPath, 'message'), { title: domain.title, title2: domain.title2, title3: 'Account Verification', domainurl: domain.url, message: 'ERROR: Invalid e-mail \"' + EscapeHtml(user.email) + '\" for user \"' + EscapeHtml(user.name) + '\". <a href="' + domain.url + '">Go to login page</a>.' });
1079 + res.render(getRenderPage('message', req), { title: domain.title, title2: domain.title2, title3: 'Account Verification', domainurl: domain.url, message: 'ERROR: Invalid e-mail \"' + EscapeHtml(user.email) + '\" for user \"' + EscapeHtml(user.name) + '\". <a href="' + domain.url + '">Go to login page</a>.' });
1080 } else {
1081 if (cookie.a == 1) {
1082 // Account email verification
1083 if (user.emailVerified == true) {
1084 parent.debug('web', 'handleCheckMailRequest: email already verified.');
1085 - res.render(obj.path.join(obj.parent.webViewsPath, 'message'), { title: domain.title, title2: domain.title2, title3: 'Account Verification', domainurl: domain.url, message: 'E-mail \"' + EscapeHtml(user.email) + '\" for user \"' + EscapeHtml(user.name) + '\" already verified. <a href="' + domain.url + '">Go to login page</a>.' });
1085 + res.render(getRenderPage('message', req), { title: domain.title, title2: domain.title2, title3: 'Account Verification', domainurl: domain.url, message: 'E-mail \"' + EscapeHtml(user.email) + '\" for user \"' + EscapeHtml(user.name) + '\" already verified. <a href="' + domain.url + '">Go to login page</a>.' });
1086 } else {
1087 obj.db.GetUserWithVerifiedEmail(domain.id, user.email, function (err, docs) {
1088 if (docs.length > 0) {
1089 parent.debug('web', 'handleCheckMailRequest: email already in use.');
1090 - res.render(obj.path.join(obj.parent.webViewsPath, 'message'), { title: domain.title, title2: domain.title2, title3: 'Account Verification', domainurl: domain.url, message: 'E-mail \"' + EscapeHtml(user.email) + '\" already in use on a different account. Change the email address and try again. <a href="' + domain.url + '">Go to login page</a>.' });
1090 + res.render(getRenderPage('message', req), { title: domain.title, title2: domain.title2, title3: 'Account Verification', domainurl: domain.url, message: 'E-mail \"' + EscapeHtml(user.email) + '\" already in use on a different account. Change the email address and try again. <a href="' + domain.url + '">Go to login page</a>.' });
1091 } else {
1092 parent.debug('web', 'handleCheckMailRequest: email verification success.');
1093
@@ -1102,7 +1102,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1102 obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
1103
1104 // Send the confirmation page
1105 - res.render(obj.path.join(obj.parent.webViewsPath, 'message'), { title: domain.title, title2: domain.title2, title3: 'Account Verification', domainurl: domain.url, message: 'Verified email <b>' + EscapeHtml(user.email) + '</b> for user account <b>' + EscapeHtml(user.name) + '</b>. <a href="' + domain.url + '">Go to login page</a>.' });
1105 + res.render(getRenderPage('message', req), { title: domain.title, title2: domain.title2, title3: 'Account Verification', domainurl: domain.url, message: 'Verified email <b>' + EscapeHtml(user.email) + '</b> for user account <b>' + EscapeHtml(user.name) + '</b>. <a href="' + domain.url + '">Go to login page</a>.' });
1106
1107 // Send a notification
1108 obj.parent.DispatchEvent([user._id], obj, { action: 'notify', value: 'Email verified:<br /><b>' + EscapeHtml(user.email) + '</b>.', nolog: 1 });
@@ -1113,7 +1113,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1113 // Account reset
1114 if (user.emailVerified != true) {
1115 parent.debug('web', 'handleCheckMailRequest: email not verified.');
1116 - res.render(obj.path.join(obj.parent.webViewsPath, 'message'), { title: domain.title, title2: domain.title2, title3: 'Account Verification', domainurl: domain.url, message: 'E-mail \"' + EscapeHtml(user.email) + '\" for user \"' + EscapeHtml(user.name) + '\" not verified. <a href="' + domain.url + '">Go to login page</a>.' });
1116 + res.render(getRenderPage('message', req), { title: domain.title, title2: domain.title2, title3: 'Account Verification', domainurl: domain.url, message: 'E-mail \"' + EscapeHtml(user.email) + '\" for user \"' + EscapeHtml(user.name) + '\" not verified. <a href="' + domain.url + '">Go to login page</a>.' });
1117 } else {
1118 // Set a temporary password
1119 obj.crypto.randomBytes(16, function (err, buf) {
@@ -1138,20 +1138,20 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1138 obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
1139
1140 // Send the new password
1141 - res.render(obj.path.join(obj.parent.webViewsPath, 'message'), { title: domain.title, title2: domain.title2, title3: 'Account Verification', domainurl: domain.url, message: '<div>Password for account <b>' + EscapeHtml(user.name) + '</b> has been reset to:</div><div style=padding:14px;font-size:18px><b>' + EscapeHtml(newpass) + '</b></div>Login and go to the \"My Account\" tab to update your password. <a href="' + domain.url + '">Go to login page</a>.' });
1141 + res.render(getRenderPage('message', req), { title: domain.title, title2: domain.title2, title3: 'Account Verification', domainurl: domain.url, message: '<div>Password for account <b>' + EscapeHtml(user.name) + '</b> has been reset to:</div><div style=padding:14px;font-size:18px><b>' + EscapeHtml(newpass) + '</b></div>Login and go to the \"My Account\" tab to update your password. <a href="' + domain.url + '">Go to login page</a>.' });
1142 parent.debug('web', 'handleCheckMailRequest: send temporary password.');
1143 }, 0);
1144 });
1145 }
1146 } else {
1147 - res.render(obj.path.join(obj.parent.webViewsPath, 'message'), { title: domain.title, title2: domain.title2, title3: 'Account Verification', domainurl: domain.url, message: 'ERROR: Invalid account check. <a href="' + domain.url + '">Go to login page</a>.' });
1147 + res.render(getRenderPage('message', req), { title: domain.title, title2: domain.title2, title3: 'Account Verification', domainurl: domain.url, message: 'ERROR: Invalid account check. <a href="' + domain.url + '">Go to login page</a>.' });
1148 }
1149 }
1150 }
1151 });
1152 }
1153 } else {
1154 - res.render(obj.path.join(obj.parent.webViewsPath, 'message'), { title: domain.title, title2: domain.title2, title3: 'Account Verification', domainurl: domain.url, message: 'ERROR: Invalid account check, verification url is only valid for 30 minutes. <a href="' + domain.url + '">Go to login page</a>.' });
1154 + res.render(getRenderPage('message', req), { title: domain.title, title2: domain.title2, title3: 'Account Verification', domainurl: domain.url, message: 'ERROR: Invalid account check, verification url is only valid for 30 minutes. <a href="' + domain.url + '">Go to login page</a>.' });
1155 }
1156 }
1157 }
@@ -1173,7 +1173,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1173 var installflags = cookie.f;
1174 if (typeof installflags != 'number') { installflags = 0; }
1175 parent.debug('web', 'handleAgentInviteRequest using cookie.');
1176 - res.render(obj.path.join(obj.parent.webViewsPath, 'agentinvite'), { title: domain.title, title2: domain.title2, domainurl: domain.url, meshid: mesh._id.split('/')[2], serverport: ((args.aliasport != null) ? args.aliasport : args.port), serverhttps: ((args.notls == true) ? '0' : '1'), servernoproxy: ((domain.agentnoproxy === true) ? '1' : '0'), meshname: encodeURIComponent(mesh.name), installflags: installflags });
1176 + res.render(getRenderPage('agentinvite', req), { title: domain.title, title2: domain.title2, domainurl: domain.url, meshid: mesh._id.split('/')[2], serverport: ((args.aliasport != null) ? args.aliasport : args.port), serverhttps: ((args.notls == true) ? '0' : '1'), servernoproxy: ((domain.agentnoproxy === true) ? '1' : '0'), meshname: encodeURIComponent(mesh.name), installflags: installflags });
1177 } else if (req.query.m != null) {
1178 // The MeshId is specified in the query string, use that
1179 var mesh = obj.meshes['mesh/' + domain.id + '/' + req.query.m.toLowerCase()];
@@ -1182,7 +1182,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1182 if (req.query.f) { installflags = parseInt(req.query.f); }
1183 if (typeof installflags != 'number') { installflags = 0; }
1184 parent.debug('web', 'handleAgentInviteRequest using meshid.');
1185 - res.render(obj.path.join(obj.parent.webViewsPath, 'agentinvite'), { title: domain.title, title2: domain.title2, domainurl: domain.url, meshid: mesh._id.split('/')[2], serverport: ((args.aliasport != null) ? args.aliasport : args.port), serverhttps: ((args.notls == true) ? '0' : '1'), servernoproxy: ((domain.agentnoproxy === true) ? '1' : '0'), meshname: encodeURIComponent(mesh.name), installflags: installflags });
1185 + res.render(getRenderPage('agentinvite', req), { title: domain.title, title2: domain.title2, domainurl: domain.url, meshid: mesh._id.split('/')[2], serverport: ((args.aliasport != null) ? args.aliasport : args.port), serverhttps: ((args.notls == true) ? '0' : '1'), servernoproxy: ((domain.agentnoproxy === true) ? '1' : '0'), meshname: encodeURIComponent(mesh.name), installflags: installflags });
1186 }
1187 }
1188
@@ -1494,18 +1494,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1494 parent.debug('web', 'handleRootRequestEx: success.');
1495 obj.db.Get('ws' + user._id, function (err, states) {
1496 var webstate = (states.length == 1) ? states[0].state : '';
1497 - if (obj.args.minify && !req.query.nominify) {
1498 - // Try to server the minified version if we can.
1499 - try {
1500 - res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'default-mobile-min' : 'default-min'), { authCookie: authCookie, viewmode: viewmode, currentNode: currentNode, logoutControl: logoutcontrol, title: domain.title, title2: domain.title2, extitle: encodeURIComponent(domain.title), extitle2: encodeURIComponent(domain.title2), domainurl: domain.url, domain: domain.id, debuglevel: parent.debugLevel, serverDnsName: obj.getWebServerName(domain), serverRedirPort: args.redirport, serverPublicPort: httpsPort, noServerBackup: (args.noserverbackup == 1 ? 1 : 0), features: features, sessiontime: args.sessiontime, mpspass: args.mpspass, passRequirements: passRequirements, webcerthash: Buffer.from(obj.webCertificateFullHashs[domain.id], 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'), footer: (domain.footer == null) ? '' : domain.footer, webstate: encodeURIComponent(webstate) });
1501 - } catch (ex) {
1502 - // In case of an exception, serve the non-minified version.
1503 - res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'default-mobile' : 'default'), { authCookie: authCookie, viewmode: viewmode, currentNode: currentNode, logoutControl: logoutcontrol, title: domain.title, title2: domain.title2, extitle: encodeURIComponent(domain.title), extitle2: encodeURIComponent(domain.title2), domainurl: domain.url, domain: domain.id, debuglevel: parent.debugLevel, serverDnsName: obj.getWebServerName(domain), serverRedirPort: args.redirport, serverPublicPort: httpsPort, noServerBackup: (args.noserverbackup == 1 ? 1 : 0), features: features, sessiontime: args.sessiontime, mpspass: args.mpspass, passRequirements: passRequirements, webcerthash: Buffer.from(obj.webCertificateFullHashs[domain.id], 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'), footer: (domain.footer == null) ? '' : domain.footer, webstate: encodeURIComponent(webstate) });
1504 - }
1505 - } else {
1506 - // Serve non-minified version of web pages.
1507 - res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'default-mobile' : 'default'), { authCookie: authCookie, viewmode: viewmode, currentNode: currentNode, logoutControl: logoutcontrol, title: domain.title, title2: domain.title2, extitle: encodeURIComponent(domain.title), extitle2: encodeURIComponent(domain.title2), domainurl: domain.url, domain: domain.id, debuglevel: parent.debugLevel, serverDnsName: obj.getWebServerName(domain), serverRedirPort: args.redirport, serverPublicPort: httpsPort, noServerBackup: (args.noserverbackup == 1 ? 1 : 0), features: features, sessiontime: args.sessiontime, mpspass: args.mpspass, passRequirements: passRequirements, webcerthash: Buffer.from(obj.webCertificateFullHashs[domain.id], 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'), footer: (domain.footer == null) ? '' : domain.footer, webstate: encodeURIComponent(webstate) });
1508 - }
1497 + res.render(getRenderPage('default', req), { authCookie: authCookie, viewmode: viewmode, currentNode: currentNode, logoutControl: logoutcontrol, title: domain.title, title2: domain.title2, extitle: encodeURIComponent(domain.title), extitle2: encodeURIComponent(domain.title2), domainurl: domain.url, domain: domain.id, debuglevel: parent.debugLevel, serverDnsName: obj.getWebServerName(domain), serverRedirPort: args.redirport, serverPublicPort: httpsPort, noServerBackup: (args.noserverbackup == 1 ? 1 : 0), features: features, sessiontime: args.sessiontime, mpspass: args.mpspass, passRequirements: passRequirements, webcerthash: Buffer.from(obj.webCertificateFullHashs[domain.id], 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'), footer: (domain.footer == null) ? '' : domain.footer, webstate: encodeURIComponent(webstate) });
1498 });
1499 } else {
1500 // Send back the login application
@@ -1572,27 +1561,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1561 var newAccountsAllowed = true;
1562 if ((domain.newaccounts !== 1) && (domain.newaccounts !== true)) { for (var i in obj.users) { if (obj.users[i].domain == domain.id) { newAccountsAllowed = false; break; } } }
1563
1575 - if (obj.args.minify && !req.query.nominify) {
1576 - // Try to server the minified version if we can.
1577 - try {
1578 - res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'login-mobile-min' : 'login-min'), { loginmode: loginmode, rootCertLink: getRootCertLink(), domainurl: domain.url, title: domain.title, title2: domain.title2, newAccount: newAccountsAllowed, newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1), serverDnsName: obj.getWebServerName(domain), serverPublicPort: httpsPort, emailcheck: emailcheck, features: features, sessiontime: args.sessiontime, passRequirements: passRequirements, footer: (domain.footer == null) ? '' : domain.footer, hkey: encodeURIComponent(hardwareKeyChallenge), message: message, passhint: passhint, welcometext: domain.welcometext?encodeURIComponent(domain.welcometext):null });
1579 - } catch (ex) {
1580 - // In case of an exception, serve the non-minified version.
1581 - res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'login-mobile' : 'login'), { loginmode: loginmode, rootCertLink: getRootCertLink(), domainurl: domain.url, title: domain.title, title2: domain.title2, newAccount: newAccountsAllowed, newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1), serverDnsName: obj.getWebServerName(domain), serverPublicPort: httpsPort, emailcheck: emailcheck, features: features, sessiontime: args.sessiontime, passRequirements: passRequirements, footer: (domain.footer == null) ? '' : domain.footer, hkey: encodeURIComponent(hardwareKeyChallenge), message: message, passhint: passhint, welcometext: domain.welcometext ? encodeURIComponent(domain.welcometext) : null });
1582 - }
1583 - } else {
1584 - // Serve non-minified version of web pages.
1585 - res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'login-mobile' : 'login'), { loginmode: loginmode, rootCertLink: getRootCertLink(), domainurl: domain.url, title: domain.title, title2: domain.title2, newAccount: newAccountsAllowed, newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1), serverDnsName: obj.getWebServerName(domain), serverPublicPort: httpsPort, emailcheck: emailcheck, features: features, sessiontime: args.sessiontime, passRequirements: passRequirements, footer: (domain.footer == null) ? '' : domain.footer, hkey: encodeURIComponent(hardwareKeyChallenge), message: message, passhint: passhint, welcometext: domain.welcometext ? encodeURIComponent(domain.welcometext) : null });
1586 - }
1587 -
1588 - /*
1589 - var xoptions = { loginmode: loginmode, rootCertLink: getRootCertLink(), title: domain.title, title2: domain.title2, newAccount: newAccountsAllowed, newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1), serverDnsName: obj.getWebServerName(domain), serverPublicPort: httpsPort, emailcheck: obj.parent.mailserver != null, features: features, footer: (domain.footer == null) ? '' : domain.footer };
1590 - var xpath = obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'login-mobile' : 'login');
1591 - console.log('Render...');
1592 - res.render(xpath, xoptions, function (err, html) {
1593 - console.log(err, html);
1594 - });
1595 - */
1564 + // Render the login page
1565 + res.render(getRenderPage('login', req), { loginmode: loginmode, rootCertLink: getRootCertLink(), domainurl: domain.url, title: domain.title, title2: domain.title2, newAccount: newAccountsAllowed, newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1), serverDnsName: obj.getWebServerName(domain), serverPublicPort: httpsPort, emailcheck: emailcheck, features: features, sessiontime: args.sessiontime, passRequirements: passRequirements, footer: (domain.footer == null) ? '' : domain.footer, hkey: encodeURIComponent(hardwareKeyChallenge), message: message, passhint: passhint, welcometext: domain.welcometext ? encodeURIComponent(domain.welcometext) : null });
1566 }
1567
1568 // Return true if it looks like we are using a real TLS certificate.
@@ -1632,9 +1602,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1602 var user = obj.users[req.session.userid];
1603 var logoutcontrol = 'Welcome ' + user.name + '.';
1604 if ((domain.ldap == null) && (domain.sspi == null) && (obj.args.user == null) && (obj.args.nousers != true)) { logoutcontrol += ' <a href=' + domain.url + 'logout?' + Math.random() + ' style=color:white>Logout</a>'; } // If a default user is in use or no user mode, don't display the logout button
1635 - res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'terms-mobile' : 'terms'), { title: domain.title, title2: domain.title2, domainurl: domain.url, terms: encodeURIComponent(parent.configurationFiles['terms.txt'].toString()), logoutControl: logoutcontrol });
1605 + res.render(getRenderPage('terms', req), { title: domain.title, title2: domain.title2, domainurl: domain.url, terms: encodeURIComponent(parent.configurationFiles['terms.txt'].toString()), logoutControl: logoutcontrol });
1606 } else {
1637 - res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'terms-mobile' : 'terms'), { title: domain.title, title2: domain.title2, domainurl: domain.url, terms: encodeURIComponent(parent.configurationFiles['terms.txt'].toString()) });
1607 + res.render(getRenderPage('terms', req), { title: domain.title, title2: domain.title2, domainurl: domain.url, terms: encodeURIComponent(parent.configurationFiles['terms.txt'].toString()) });
1608 }
1609 } else {
1610 // See if there is a terms.txt file in meshcentral-data
@@ -1650,9 +1620,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1620 var user = obj.users[req.session.userid];
1621 var logoutcontrol = 'Welcome ' + user.name + '.';
1622 if ((domain.ldap == null) && (domain.sspi == null) && (obj.args.user == null) && (obj.args.nousers != true)) { logoutcontrol += ' <a href=' + domain.url + 'logout?' + Math.random() + ' style=color:white>Logout</a>'; } // If a default user is in use or no user mode, don't display the logout button
1653 - res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'terms-mobile' : 'terms'), { title: domain.title, title2: domain.title2, domainurl: domain.url, terms: encodeURIComponent(data), logoutControl: logoutcontrol });
1623 + res.render(getRenderPage('terms', req), { title: domain.title, title2: domain.title2, domainurl: domain.url, terms: encodeURIComponent(data), logoutControl: logoutcontrol });
1624 } else {
1655 - res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'terms-mobile' : 'terms'), { title: domain.title, title2: domain.title2, domainurl: domain.url, terms: encodeURIComponent(data) });
1625 + res.render(getRenderPage('terms', req), { title: domain.title, title2: domain.title2, domainurl: domain.url, terms: encodeURIComponent(data) });
1626 }
1627 });
1628 } else {
@@ -1664,9 +1634,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1634 var user = obj.users[req.session.userid];
1635 var logoutcontrol = 'Welcome ' + user.name + '.';
1636 if ((domain.ldap == null) && (domain.sspi == null) && (obj.args.user == null) && (obj.args.nousers != true)) { logoutcontrol += ' <a href=' + domain.url + 'logout?' + Math.random() + ' style=color:white>Logout</a>'; } // If a default user is in use or no user mode, don't display the logout button
1667 - res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'terms-mobile' : 'terms'), { title: domain.title, title2: domain.title2, domainurl: domain.url, logoutControl: logoutcontrol });
1637 + res.render(getRenderPage('terms', req), { title: domain.title, title2: domain.title2, domainurl: domain.url, logoutControl: logoutcontrol });
1638 } else {
1669 - res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'terms-mobile' : 'terms'), { title: domain.title, title2: domain.title2, domainurl: domain.url });
1639 + res.render(getRenderPage('terms', req), { title: domain.title, title2: domain.title2, domainurl: domain.url });
1640 }
1641 }
1642 }
@@ -1681,7 +1651,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1651 var webRtcConfig = null;
1652 if (obj.parent.config.settings && obj.parent.config.settings.webrtconfig && (typeof obj.parent.config.settings.webrtconfig == 'object')) { webRtcConfig = encodeURIComponent(JSON.stringify(obj.parent.config.settings.webrtconfig)); }
1653 res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' });
1684 - res.render(obj.path.join(obj.parent.webViewsPath, 'messenger'), { webrtconfig: webRtcConfig, domainurl: domain.url });
1654 + res.render(getRenderPage('messenger', req), { webrtconfig: webRtcConfig, domainurl: domain.url });
1655 }
1656
1657 // Returns the server root certificate encoded in base64
@@ -1817,10 +1787,10 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1787 }
1788 try { res.sendFile(obj.path.resolve(__dirname, path)); } catch (e) { res.sendStatus(404); }
1789 } else {
1820 - res.render(obj.path.join(obj.parent.webViewsPath, 'download'), { rootCertLink: getRootCertLink(), title: domain.title, title2: domain.title2, domainurl: domain.url, message: "<a href='" + req.path + "?download=1'>" + filename + "</a>, " + stat.size + " byte" + ((stat.size < 2) ? '' : 's') + "." });
1790 + res.render(getRenderPage('download', req), { rootCertLink: getRootCertLink(), title: domain.title, title2: domain.title2, domainurl: domain.url, message: "<a href='" + req.path + "?download=1'>" + filename + "</a>, " + stat.size + " byte" + ((stat.size < 2) ? '' : 's') + "." });
1791 }
1792 } else {
1823 - res.render(obj.path.join(obj.parent.webViewsPath, 'download'), { rootCertLink: getRootCertLink(), title: domain.title, title2: domain.title2, domainurl: domain.url, message: "Invalid file link, please check the URL again." });
1793 + res.render(getRenderPage('download', req), { rootCertLink: getRootCertLink(), title: domain.title, title2: domain.title2, domainurl: domain.url, message: "Invalid file link, please check the URL again." });
1794 }
1795 }
1796
@@ -3260,7 +3230,10 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
3230
3231 obj.app.get(url + 'stop', function (req, res) { res.send('Stopping Server, <a href="' + url + '">click here to login</a>.'); setTimeout(function () { parent.Stop(); }, 500); });
3232
3263 - // Indicates to ExpressJS that the public folder should be used to serve static files.
3233 + // Indicates to ExpressJS that the override public folder should be used to serve static files.
3234 + if (obj.parent.webPublicOverridePath != null) { obj.app.use(url, obj.express.static(obj.parent.webPublicOverridePath, { maxAge: '1h' })); }
3235 +
3236 + // Indicates to ExpressJS that the default public folder should be used to serve static files.
3237 obj.app.use(url, obj.express.static(obj.parent.webPublicPath, { maxAge: '1h' }));
3238
3239 // Start regular disconnection list flush every 2 minutes.
@@ -3272,7 +3245,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
3245 obj.app.use(function (req, res, next) {
3246 parent.debug('web', '404 Error ' + req.url);
3247 var domain = getDomain(req);
3275 - res.status(404).render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'error404-mobile' : 'error404'), { title: domain.title, title2: domain.title2 });
3248 + res.status(404).render(getRenderPage('error404', req), { title: domain.title, title2: domain.title2 });
3249 });
3250 }
3251
@@ -3596,6 +3569,45 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
3569 return user2;
3570 }
3571
3572 + // Return the correct render page given mobile, minify and override path.
3573 + function getRenderPage(pagename, req) {
3574 + var mobile = isMobileBrowser(req), minify = obj.args.minify && !req.query.nominify, p;
3575 + if (mobile) {
3576 + if (obj.parent.webViewsOverridePath != null) {
3577 + if (minify) {
3578 + p = obj.path.join(obj.parent.webViewsOverridePath, pagename + '-mobile-min');
3579 + if (obj.fs.existsSync(p + '.handlebars')) { return p; } // Mobile + Minify + Override document
3580 + } else {
3581 + p = obj.path.join(obj.parent.webViewsOverridePath, pagename + '-mobile');
3582 + if (obj.fs.existsSync(p + '.handlebars')) { return p; } // Mobile + Override document
3583 + }
3584 + }
3585 + if (minify) {
3586 + p = obj.path.join(obj.parent.webViewsPath, pagename + '-mobile-min');
3587 + if (obj.fs.existsSync(p + '.handlebars')) { return p; } // Mobile + Minify document
3588 + } else {
3589 + p = obj.path.join(obj.parent.webViewsPath, pagename + '-mobile');
3590 + if (obj.fs.existsSync(p + '.handlebars')) { return p; } // Mobile document
3591 + }
3592 + }
3593 + if (obj.parent.webViewsOverridePath != null) {
3594 + if (minify) {
3595 + p = obj.path.join(obj.parent.webViewsOverridePath, pagename + '-min');
3596 + if (obj.fs.existsSync(p + '.handlebars')) { return p; } // Minify + Override document
3597 + } else {
3598 + p = obj.path.join(obj.parent.webViewsOverridePath, pagename);
3599 + if (obj.fs.existsSync(p + '.handlebars')) { return p; } // Override document
3600 + }
3601 + }
3602 + if (minify) {
3603 + p = obj.path.join(obj.parent.webViewsPath, pagename + '-min');
3604 + if (obj.fs.existsSync(p + '.handlebars')) { return p; } // Minify document
3605 + } else {
3606 + p = obj.path.join(obj.parent.webViewsPath, pagename);
3607 + if (obj.fs.existsSync(p + '.handlebars')) { return p; } // Default document
3608 + }
3609 + }
3610 +
3611 // Return true if a mobile browser is detected.
3612 // This code comes from "http://detectmobilebrowsers.com/" and was modified, This is free and unencumbered software released into the public domain. For more information, please refer to the http://unlicense.org/
3613 function isMobileBrowser(req) {