From 5a418b615b8198f61ea24110e7fb524a70d2c003 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Wed, 25 Mar 2020 13:21:14 -0700 Subject: [PATCH] Added support for per-domain web folder. --- meshcentral.js | 13 +++++- meshuser.js | 12 ++++-- webserver.js | 113 +++++++++++++++++++++++++++++++++---------------- 3 files changed, 96 insertions(+), 42 deletions(-) diff --git a/meshcentral.js b/meshcentral.js index 7a7044b5..63e6fe1a 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -178,7 +178,7 @@ function CreateMeshCentralServer(config, args) { didSomething = true; } - // Check is "meshcentral-web" exists, if so, translate all pages in that folder. + // Check if "meshcentral-web" exists, if so, translate all pages in that folder. if (obj.webViewsOverridePath != null) { didSomething = true; var files = obj.fs.readdirSync(obj.webViewsOverridePath); @@ -972,6 +972,15 @@ function CreateMeshCentralServer(config, args) { obj.config.domains[i].newaccountsrights = newAccRights; } if (obj.config.domains[i].newaccountsrights && (typeof (obj.config.domains[i].newaccountsrights) != 'number')) { delete obj.config.domains[i].newaccountsrights; } + + // Check if there is a web views path and/or web public path for this domain + if ((__dirname.endsWith('/node_modules/meshcentral')) || (__dirname.endsWith('\\node_modules\\meshcentral')) || (__dirname.endsWith('/node_modules/meshcentral/')) || (__dirname.endsWith('\\node_modules\\meshcentral\\'))) { + if ((obj.config.domains[i].webviewspath == null) && (obj.fs.existsSync(obj.path.join(__dirname, '../../meshcentral-web-' + i + '/views')))) { obj.config.domains[i].webviewspath = obj.path.join(__dirname, '../../meshcentral-web-' + i + '/views'); } + if ((obj.config.domains[i].webpublicpath == null) && (obj.fs.existsSync(obj.path.join(__dirname, '../../meshcentral-web-' + i + '/public')))) { obj.config.domains[i].webpublicpath = obj.path.join(__dirname, '../../meshcentral-web-' + i + '/public'); } + } else { + if ((obj.config.domains[i].webviewspath == null) && (obj.fs.existsSync(obj.path.join(__dirname, '../meshcentral-web-' + i + '/views')))) { obj.config.domains[i].webviewspath = obj.path.join(__dirname, '../meshcentral-web-' + i + '/views'); } + if ((obj.config.domains[i].webpublicpath == null) && (obj.fs.existsSync(obj.path.join(__dirname, '../meshcentral-web-' + i + '/public')))) { obj.config.domains[i].webpublicpath = obj.path.join(__dirname, '../meshcentral-web-' + i + '/public'); } + } } // Log passed arguments into Windows Service Log @@ -2395,7 +2404,7 @@ function mainStart() { // Lowercase the auth value if present for (var i in config.domains) { if (typeof config.domains[i].auth == 'string') { config.domains[i].auth = config.domains[i].auth.toLowerCase(); } } - // Check is Windows SSPI and YubiKey OTP will be used + // Check if Windows SSPI and YubiKey OTP will be used var sspi = false; var ldap = false; var allsspi = true; diff --git a/meshuser.js b/meshuser.js index 7eaca491..f51982cf 100644 --- a/meshuser.js +++ b/meshuser.js @@ -2415,6 +2415,12 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use break; } + // Convert user names to userid's + if (command.userids == null) { + command.userids = []; + for (var i in command.usernames) { command.userids.push('user/' + domain.id + '/' + command.usernames[i].toLowerCase()); } + } + // TODO //console.log(command); @@ -3192,7 +3198,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } case 'otp-hkey-remove': { - // Check is 2-step login is supported + // Check if 2-step login is supported const twoStepLoginSupported = ((parent.parent.config.settings.no2factorauth !== true) && (domain.auth != 'sspi') && (parent.parent.certificates.CommonName.indexOf('.') != -1) && (args.nousers !== true)); if (twoStepLoginSupported == false || command.index == null) break; @@ -3218,7 +3224,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use var yubikeyotp = null; try { yubikeyotp = require('yubikeyotp'); } catch (ex) { } - // Check is 2-step login is supported + // Check if 2-step login is supported const twoStepLoginSupported = ((parent.parent.config.settings.no2factorauth !== true) && (domain.auth != 'sspi') && (parent.parent.certificates.CommonName.indexOf('.') != -1) && (args.nousers !== true)); if ((yubikeyotp == null) || (twoStepLoginSupported == false) || (typeof command.otp != 'string')) { ws.send(JSON.stringify({ action: 'otp-hkey-yubikey-add', result: false, name: command.name })); @@ -3267,7 +3273,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } case 'webauthn-startregister': { - // Check is 2-step login is supported + // Check if 2-step login is supported const twoStepLoginSupported = ((parent.parent.config.settings.no2factorauth !== true) && (domain.auth != 'sspi') && (parent.parent.certificates.CommonName.indexOf('.') != -1) && (args.nousers !== true)); if ((twoStepLoginSupported == false) || (command.name == null)) break; diff --git a/webserver.js b/webserver.js index 2c76e53a..84da978d 100644 --- a/webserver.js +++ b/webserver.js @@ -1173,28 +1173,28 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { var idsplit = cookie.u.split('/'); if ((idsplit.length != 2) || (idsplit[0] != domain.id)) { parent.debug('web', 'handleCheckMailRequest: Invalid domain.'); - render(req, res, getRenderPage('message', req), getRenderArgs({ title3: 'Account Verification', message: 'ERROR: Invalid domain. Go to login page.' }, domain)); + render(req, res, getRenderPage('message', req, domain), getRenderArgs({ title3: 'Account Verification', message: 'ERROR: Invalid domain. Go to login page.' }, domain)); } else { obj.db.Get('user/' + cookie.u.toLowerCase(), function (err, docs) { if (docs.length == 0) { parent.debug('web', 'handleCheckMailRequest: Invalid username.'); - render(req, res, getRenderPage('message', req), getRenderArgs({ title3: 'Account Verification', message: 'ERROR: Invalid username \"' + EscapeHtml(idsplit[1]) + '\". Go to login page.' }, domain)); + render(req, res, getRenderPage('message', req, domain), getRenderArgs({ title3: 'Account Verification', message: 'ERROR: Invalid username \"' + EscapeHtml(idsplit[1]) + '\". Go to login page.' }, domain)); } else { var user = docs[0]; if (user.email != cookie.e) { parent.debug('web', 'handleCheckMailRequest: Invalid e-mail.'); - render(req, res, getRenderPage('message', req), getRenderArgs({ title3: 'Account Verification', message: 'ERROR: Invalid e-mail \"' + EscapeHtml(user.email) + '\" for user \"' + EscapeHtml(user.name) + '\". Go to login page.' }, domain)); + render(req, res, getRenderPage('message', req, domain), getRenderArgs({ title3: 'Account Verification', message: 'ERROR: Invalid e-mail \"' + EscapeHtml(user.email) + '\" for user \"' + EscapeHtml(user.name) + '\". Go to login page.' }, domain)); } else { if (cookie.a == 1) { // Account email verification if (user.emailVerified == true) { parent.debug('web', 'handleCheckMailRequest: email already verified.'); - render(req, res, getRenderPage('message', req), getRenderArgs({ title3: 'Account Verification', message: 'E-mail \"' + EscapeHtml(user.email) + '\" for user \"' + EscapeHtml(user.name) + '\" already verified. Go to login page.' }, domain)); + render(req, res, getRenderPage('message', req, domain), getRenderArgs({ title3: 'Account Verification', message: 'E-mail \"' + EscapeHtml(user.email) + '\" for user \"' + EscapeHtml(user.name) + '\" already verified. Go to login page.' }, domain)); } else { obj.db.GetUserWithVerifiedEmail(domain.id, user.email, function (err, docs) { if (docs.length > 0) { parent.debug('web', 'handleCheckMailRequest: email already in use.'); - render(req, res, getRenderPage('message', req), getRenderArgs({ title3: 'Account Verification', message: 'E-mail \"' + EscapeHtml(user.email) + '\" already in use on a different account. Change the email address and try again. Go to login page.' }, domain)); + render(req, res, getRenderPage('message', req, domain), getRenderArgs({ title3: 'Account Verification', message: 'E-mail \"' + EscapeHtml(user.email) + '\" already in use on a different account. Change the email address and try again. Go to login page.' }, domain)); } else { parent.debug('web', 'handleCheckMailRequest: email verification success.'); @@ -1209,7 +1209,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, event); // Send the confirmation page - render(req, res, getRenderPage('message', req), getRenderArgs({ title3: 'Account Verification', message: 'Verified email ' + EscapeHtml(user.email) + ' for user account ' + EscapeHtml(user.name) + '. Go to login page.' }, domain)); + render(req, res, getRenderPage('message', req, domain), getRenderArgs({ title3: 'Account Verification', message: 'Verified email ' + EscapeHtml(user.email) + ' for user account ' + EscapeHtml(user.name) + '. Go to login page.' }, domain)); // Send a notification obj.parent.DispatchEvent([user._id], obj, { action: 'notify', value: 'Email verified:
' + EscapeHtml(user.email) + '.', nolog: 1, id: Math.random() }); @@ -1220,7 +1220,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Account reset if (user.emailVerified != true) { parent.debug('web', 'handleCheckMailRequest: email not verified.'); - render(req, res, getRenderPage('message', req), getRenderArgs({ title3: 'Account Verification', message: 'E-mail \"' + EscapeHtml(user.email) + '\" for user \"' + EscapeHtml(user.name) + '\" not verified. Go to login page.' }, domain)); + render(req, res, getRenderPage('message', req, domain), getRenderArgs({ title3: 'Account Verification', message: 'E-mail \"' + EscapeHtml(user.email) + '\" for user \"' + EscapeHtml(user.name) + '\" not verified. Go to login page.' }, domain)); } else { // Set a temporary password obj.crypto.randomBytes(16, function (err, buf) { @@ -1245,20 +1245,20 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, event); // Send the new password - render(req, res, getRenderPage('message', req), getRenderArgs({ title3: 'Account Verification', message: '
Password for account ' + EscapeHtml(user.name) + ' has been reset to:
' + EscapeHtml(newpass) + '
Login and go to the \"My Account\" tab to update your password. Go to login page.' }, domain)); + render(req, res, getRenderPage('message', req, domain), getRenderArgs({ title3: 'Account Verification', message: '
Password for account ' + EscapeHtml(user.name) + ' has been reset to:
' + EscapeHtml(newpass) + '
Login and go to the \"My Account\" tab to update your password. Go to login page.' }, domain)); parent.debug('web', 'handleCheckMailRequest: send temporary password.'); }, 0); }); } } else { - render(req, res, getRenderPage('message', req), getRenderArgs({ title3: 'Account Verification', message: 'ERROR: Invalid account check. Go to login page.' }, domain)); + render(req, res, getRenderPage('message', req, domain), getRenderArgs({ title3: 'Account Verification', message: 'ERROR: Invalid account check. Go to login page.' }, domain)); } } } }); } } else { - render(req, res, getRenderPage('message', req), getRenderArgs({ title3: 'Account Verification', message: 'ERROR: Invalid account check, verification url is only valid for 30 minutes. Go to login page.' }, domain)); + render(req, res, getRenderPage('message', req, domain), getRenderArgs({ title3: 'Account Verification', message: 'ERROR: Invalid account check, verification url is only valid for 30 minutes. Go to login page.' }, domain)); } } } @@ -1268,7 +1268,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { const domain = getDomain(req); if (domain == null) { parent.debug('web', 'handleInviteRequest: failed checks.'); res.sendStatus(404); return; } if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key - if ((req.body.inviteCode == null) || (req.body.inviteCode == '')) { render(req, res, getRenderPage('invite', req), getRenderArgs({ messageid: 0 }, domain)); return; } // No invitation code + if ((req.body.inviteCode == null) || (req.body.inviteCode == '')) { render(req, res, getRenderPage('invite', req, domain), getRenderArgs({ messageid: 0 }, domain)); return; } // No invitation code // Each for a device group that has this invite code. for (var i in obj.meshes) { @@ -1279,7 +1279,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { } } - render(req, res, getRenderPage('invite', req), getRenderArgs({ messageid: 100 }, domain)); // Bad invitation code + render(req, res, getRenderPage('invite', req, domain), getRenderArgs({ messageid: 100 }, domain)); // Bad invitation code } // Called to process an agent invite request @@ -1297,7 +1297,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { var installflags = cookie.f; if (typeof installflags != 'number') { installflags = 0; } parent.debug('web', 'handleAgentInviteRequest using cookie.'); - render(req, res, getRenderPage('agentinvite', req), getRenderArgs({ 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 }, domain)); + render(req, res, getRenderPage('agentinvite', req, domain), getRenderArgs({ 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 }, domain)); } else if (req.query.m != null) { // The MeshId is specified in the query string, use that var mesh = obj.meshes['mesh/' + domain.id + '/' + req.query.m.toLowerCase()]; @@ -1306,7 +1306,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if (req.query.f) { installflags = parseInt(req.query.f); } if (typeof installflags != 'number') { installflags = 0; } parent.debug('web', 'handleAgentInviteRequest using meshid.'); - render(req, res, getRenderPage('agentinvite', req), getRenderArgs({ 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 }, domain)); + render(req, res, getRenderPage('agentinvite', req, domain), getRenderArgs({ 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 }, domain)); } } @@ -1573,7 +1573,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // If a user exists and is logged in, serve the default app, otherwise server the login app. if (req.session && req.session.userid && obj.users[req.session.userid]) { var user = obj.users[req.session.userid]; - if (req.session.domainid != domain.id) { // Check is the session is for the correct domain + if (req.session.domainid != domain.id) { // Check if the session is for the correct domain parent.debug('web', 'handleRootRequestEx: incorrect domain.'); req.session = null; res.redirect(domain.url + getQueryPortion(req)); // BAD*** @@ -1674,7 +1674,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { for (var i in domain.forceduserwebstate) { webstate2[i] = domain.forceduserwebstate[i]; } webstate = JSON.stringify(webstate2); } - render(req, res, getRenderPage('default', req), getRenderArgs({ authCookie: authCookie, authRelayCookie: authRelayCookie, viewmode: viewmode, currentNode: currentNode, logoutControls: JSON.stringify(logoutcontrols), 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), pluginHandler: (parent.pluginHandler == null) ? 'null' : parent.pluginHandler.prepExports(), StartGeoLocation: StartGeoLocation, EndGeoLocation: EndGeoLocation, StartGeoLocationJS: StartGeoLocationJS, EndGeoLocationJS: EndGeoLocationJS }, domain)); + render(req, res, getRenderPage('default', req, domain), getRenderArgs({ authCookie: authCookie, authRelayCookie: authRelayCookie, viewmode: viewmode, currentNode: currentNode, logoutControls: JSON.stringify(logoutcontrols), 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), pluginHandler: (parent.pluginHandler == null) ? 'null' : parent.pluginHandler.prepExports(), StartGeoLocation: StartGeoLocation, EndGeoLocation: EndGeoLocation, StartGeoLocationJS: StartGeoLocationJS, EndGeoLocationJS: EndGeoLocationJS }, domain)); }); } else { // Send back the login application @@ -1745,7 +1745,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.email2factor == false)) { otpemail = false; } // Render the login page - render(req, res, getRenderPage('login', req), getRenderArgs({ loginmode: loginmode, rootCertLink: getRootCertLink(), 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), messageid: msgid, passhint: passhint, welcometext: domain.welcometext ? encodeURIComponent(domain.welcometext).split('\'').join('\\\'') : null, hwstate: hwstate, otpemail: otpemail }, domain)); + render(req, res, getRenderPage('login', req, domain), getRenderArgs({ loginmode: loginmode, rootCertLink: getRootCertLink(), 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), messageid: msgid, passhint: passhint, welcometext: domain.welcometext ? encodeURIComponent(domain.welcometext).split('\'').join('\\\'') : null, hwstate: hwstate, otpemail: otpemail }, domain)); } // Handle a post request on the root @@ -1817,7 +1817,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { const authCookie = obj.parent.encodeCookie({ userid: user._id, domainid: domain.id, ip: cleanRemoteAddr(req.ip) }, obj.parent.loginCookieEncryptionKey); const authRelayCookie = obj.parent.encodeCookie({ ruserid: user._id, domainid: domain.id }, obj.parent.loginCookieEncryptionKey); var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified - render(req, res, getRenderPage('xterm', req), getRenderArgs({ serverDnsName: obj.getWebServerName(domain), serverRedirPort: args.redirport, serverPublicPort: httpsPort, authCookie: authCookie, authRelayCookie: authRelayCookie, logoutControls: JSON.stringify(logoutcontrols), name: EscapeHtml(node.name) }, domain)); + render(req, res, getRenderPage('xterm', req, domain), getRenderArgs({ serverDnsName: obj.getWebServerName(domain), serverRedirPort: args.redirport, serverPublicPort: httpsPort, authCookie: authCookie, authRelayCookie: authRelayCookie, logoutControls: JSON.stringify(logoutcontrols), name: EscapeHtml(node.name) }, domain)); }); } else { res.redirect(domain.url + getQueryPortion(req)); @@ -1836,14 +1836,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Send the terms from the database res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' }); if (req.session && req.session.userid) { - if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url + getQueryPortion(req)); return; } // Check is the session is for the correct domain + if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url + getQueryPortion(req)); return; } // Check if the session is for the correct domain var user = obj.users[req.session.userid]; var logoutcontrols = { name: user.name }; var extras = (req.query.key != null) ? ('&key=' + req.query.key) : ''; if ((domain.ldap == null) && (domain.sspi == null) && (obj.args.user == null) && (obj.args.nousers != true)) { logoutcontrols.logoutUrl = (domain.url + 'logout?' + Math.random() + extras); } // If a default user is in use or no user mode, don't display the logout button - render(req, res, getRenderPage('terms', req), getRenderArgs({ terms: encodeURIComponent(parent.configurationFiles['terms.txt'].toString()).split('\'').join('\\\''), logoutControls: JSON.stringify(logoutcontrols) }, domain)); + render(req, res, getRenderPage('terms', req, domain), getRenderArgs({ terms: encodeURIComponent(parent.configurationFiles['terms.txt'].toString()).split('\'').join('\\\''), logoutControls: JSON.stringify(logoutcontrols) }, domain)); } else { - render(req, res, getRenderPage('terms', req), getRenderArgs({ terms: encodeURIComponent(parent.configurationFiles['terms.txt'].toString()).split('\'').join('\\\''), logoutControls: '{}' }, domain)); + render(req, res, getRenderPage('terms', req, domain), getRenderArgs({ terms: encodeURIComponent(parent.configurationFiles['terms.txt'].toString()).split('\'').join('\\\''), logoutControls: '{}' }, domain)); } } else { // See if there is a terms.txt file in meshcentral-data @@ -1855,14 +1855,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Send the terms from terms.txt res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' }); if (req.session && req.session.userid) { - if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url + getQueryPortion(req)); return; } // Check is the session is for the correct domain + if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url + getQueryPortion(req)); return; } // Check if the session is for the correct domain var user = obj.users[req.session.userid]; var logoutcontrols = { name: user.name }; var extras = (req.query.key != null) ? ('&key=' + req.query.key) : ''; if ((domain.ldap == null) && (domain.sspi == null) && (obj.args.user == null) && (obj.args.nousers != true)) { logoutcontrols.logoutUrl = (domain.url + 'logout?' + Math.random() + extras); } // If a default user is in use or no user mode, don't display the logout button - render(req, res, getRenderPage('terms', req), getRenderArgs({ terms: encodeURIComponent(data).split('\'').join('\\\''), logoutControls: JSON.stringify(logoutcontrols) }, domain)); + render(req, res, getRenderPage('terms', req, domain), getRenderArgs({ terms: encodeURIComponent(data).split('\'').join('\\\''), logoutControls: JSON.stringify(logoutcontrols) }, domain)); } else { - render(req, res, getRenderPage('terms', req), getRenderArgs({ terms: encodeURIComponent(data).split('\'').join('\\\''), logoutControls: '{}' }, domain)); + render(req, res, getRenderPage('terms', req, domain), getRenderArgs({ terms: encodeURIComponent(data).split('\'').join('\\\''), logoutControls: '{}' }, domain)); } }); } else { @@ -1870,14 +1870,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { parent.debug('web', 'handleTermsRequest: sending default terms'); res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' }); if (req.session && req.session.userid) { - if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url + getQueryPortion(req)); return; } // Check is the session is for the correct domain + if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url + getQueryPortion(req)); return; } // Check if the session is for the correct domain var user = obj.users[req.session.userid]; var logoutcontrols = { name: user.name }; var extras = (req.query.key != null) ? ('&key=' + req.query.key) : ''; if ((domain.ldap == null) && (domain.sspi == null) && (obj.args.user == null) && (obj.args.nousers != true)) { logoutcontrols.logoutUrl = (domain.url + 'logout?' + Math.random() + extras); } // If a default user is in use or no user mode, don't display the logout button - render(req, res, getRenderPage('terms', req), getRenderArgs({ logoutControls: JSON.stringify(logoutcontrols) }, domain)); + render(req, res, getRenderPage('terms', req, domain), getRenderArgs({ logoutControls: JSON.stringify(logoutcontrols) }, domain)); } else { - render(req, res, getRenderPage('terms', req), getRenderArgs({ logoutControls: '{}' }, domain)); + render(req, res, getRenderPage('terms', req, domain), getRenderArgs({ logoutControls: '{}' }, domain)); } } } @@ -1892,7 +1892,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { var webRtcConfig = null; 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)); } res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' }); - render(req, res, getRenderPage('messenger', req), getRenderArgs({ webrtconfig: webRtcConfig }, domain)); + render(req, res, getRenderPage('messenger', req, domain), getRenderArgs({ webrtconfig: webRtcConfig }, domain)); } // Returns the server root certificate encoded in base64 @@ -2053,10 +2053,10 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { } try { res.sendFile(obj.path.resolve(__dirname, path)); } catch (e) { res.sendStatus(404); } } else { - render(req, res, getRenderPage('download', req), getRenderArgs({ rootCertLink: getRootCertLink(), message: "" + filename + ", " + stat.size + " byte" + ((stat.size < 2) ? '' : 's') + "." }, domain)); + render(req, res, getRenderPage('download', req, domain), getRenderArgs({ rootCertLink: getRootCertLink(), message: "" + filename + ", " + stat.size + " byte" + ((stat.size < 2) ? '' : 's') + "." }, domain)); } } else { - render(req, res, getRenderPage('download', req), getRenderArgs({ rootCertLink: getRootCertLink(), message: "Invalid file link, please check the URL again." }, domain)); + render(req, res, getRenderPage('download', req, domain), getRenderArgs({ rootCertLink: getRootCertLink(), message: "Invalid file link, please check the URL again." }, domain)); } } @@ -2077,7 +2077,10 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { } } - if (parent.webPublicOverridePath && obj.fs.existsSync(obj.path.join(obj.parent.webPublicOverridePath, 'images/logoback.png'))) { + if ((domain != null) && (domain.webpublicpath != null) && (obj.fs.existsSync(obj.path.join(domain.webpublicpath, 'images/logoback.png')))) { + // Use the domain logo picture + try { res.sendFile(obj.path.join(domain.webpublicpath, 'images/logoback.png')); } catch (ex) { res.sendStatus(404); } + } else if (parent.webPublicOverridePath && obj.fs.existsSync(obj.path.join(obj.parent.webPublicOverridePath, 'images/logoback.png'))) { // Use the override logo picture try { res.sendFile(obj.path.join(obj.parent.webPublicOverridePath, 'images/logoback.png')); } catch (ex) { res.sendStatus(404); } } else { @@ -2166,7 +2169,17 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { try { res.sendFile(obj.path.join(obj.parent.datapath, domain.welcomepicture)); return; } catch (ex) { } } - if (parent.webPublicOverridePath) { + if ((domain != null) && (domain.webpublicpath != null)) { + obj.fs.exists(obj.path.join(domain.webpublicpath, 'images/mainwelcome.jpg'), function (exists) { + if (exists) { + // Use the domain logo picture + try { res.sendFile(obj.path.join(domain.webpublicpath, 'images/mainwelcome.jpg')); } catch (ex) { res.sendStatus(404); } + } else { + // Use the default logo picture + try { res.sendFile(obj.path.join(obj.parent.webPublicPath, 'images/mainwelcome.jpg')); } catch (ex) { res.sendStatus(404); } + } + }); + } else if (parent.webPublicOverridePath) { obj.fs.exists(obj.path.join(obj.parent.webPublicOverridePath, 'images/mainwelcome.jpg'), function (exists) { if (exists) { // Use the override logo picture @@ -2189,7 +2202,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { parent.debug('web', 'handlePlayerRequest: sending player'); res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' }); - render(req, res, getRenderPage('player', req), getRenderArgs({}, domain)); + render(req, res, getRenderPage('player', req, domain), getRenderArgs({}, domain)); } // Handle domain redirection @@ -3663,7 +3676,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Use the data folder server picture try { res.sendFile(p); } catch (ex) { res.sendStatus(404); } } else { - if (parent.webPublicOverridePath && obj.fs.existsSync(obj.path.join(obj.parent.webPublicOverridePath, 'images/server-256.png'))) { + var domain = getDomain(req); + if ((domain != null) && (domain.webpublicpath != null) && (obj.fs.existsSync(obj.path.join(domain.webpublicpath, 'images/server-256.png')))) { + // Use the domain server picture + try { res.sendFile(obj.path.join(domain.webpublicpath, 'images/server-256.png')); } catch (ex) { res.sendStatus(404); } + } else if (parent.webPublicOverridePath && obj.fs.existsSync(obj.path.join(obj.parent.webPublicOverridePath, 'images/server-256.png'))) { // Use the override server picture try { res.sendFile(obj.path.join(obj.parent.webPublicOverridePath, 'images/server-256.png')); } catch (ex) { res.sendStatus(404); } } else { @@ -3732,7 +3749,13 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { //obj.app.get(url + 'stop', function (req, res) { res.send('Stopping Server, click here to login.'); setTimeout(function () { parent.Stop(); }, 500); }); // Indicates to ExpressJS that the override public folder should be used to serve static files. - if (obj.parent.webPublicOverridePath != null) { obj.app.use(url, obj.express.static(obj.parent.webPublicOverridePath)); } + if (parent.config.domains[i].webpublicpath != null) { + // Use domain public path + obj.app.use(url, obj.express.static(parent.config.domains[i].webpublicpath)); + } else if (obj.parent.webPublicOverridePath != null) { + // Use override path + obj.app.use(url, obj.express.static(obj.parent.webPublicOverridePath)); + } // Indicates to ExpressJS that the default public folder should be used to serve static files. obj.app.use(url, obj.express.static(obj.parent.webPublicPath)); @@ -3748,7 +3771,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { var domain = getDomain(req); if ((domain == null) || (domain.auth == 'sspi')) { res.sendStatus(404); return; } if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key - res.status(404).render(getRenderPage('error404', req), getRenderArgs({}, domain)); + res.status(404).render(getRenderPage('error404', req, domain), getRenderArgs({}, domain)); }); } @@ -4384,9 +4407,17 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { } // Return the correct render page given mobile, minify and override path. - function getRenderPage(pagename, req) { + function getRenderPage(pagename, req, domain) { var mobile = isMobileBrowser(req), minify = obj.args.minify && !req.query.nominify, p; if (mobile) { + if ((domain != null) && (domain.webviewspath != null)) { // If the domain has a web views path, use that first + if (minify) { + p = obj.path.join(domain.webviewspath, pagename + '-mobile-min'); + if (obj.fs.existsSync(p + '.handlebars')) { return p; } // Mobile + Minify + Override document + } + p = obj.path.join(domain.webviewspath, pagename + '-mobile'); + if (obj.fs.existsSync(p + '.handlebars')) { return p; } // Mobile + Override document + } if (obj.parent.webViewsOverridePath != null) { if (minify) { p = obj.path.join(obj.parent.webViewsOverridePath, pagename + '-mobile-min'); @@ -4402,6 +4433,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { p = obj.path.join(obj.parent.webViewsPath, pagename + '-mobile'); if (obj.fs.existsSync(p + '.handlebars')) { return p; } // Mobile document } + if ((domain != null) && (domain.webviewspath != null)) { // If the domain has a web views path, use that first + if (minify) { + p = obj.path.join(domain.webviewspath, pagename + '-min'); + if (obj.fs.existsSync(p + '.handlebars')) { return p; } // Minify + Override document + } + p = obj.path.join(domain.webviewspath, pagename); + if (obj.fs.existsSync(p + '.handlebars')) { return p; } // Override document + } if (obj.parent.webViewsOverridePath != null) { if (minify) { p = obj.path.join(obj.parent.webViewsOverridePath, pagename + '-min');