Improved server tracing, added 1h static file max-age.

This commit is contained in:
Ylian Saint-Hilaire 2019-08-23 15:45:36 -07:00
parent 6ebcaf493b
commit a8e15a4fa9
3 changed files with 97 additions and 23 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "meshcentral", "name": "meshcentral",
"version": "0.4.0-f", "version": "0.4.0-g",
"keywords": [ "keywords": [
"Remote Management", "Remote Management",
"Intel AMT", "Intel AMT",

File diff suppressed because one or more lines are too long

View File

@ -513,8 +513,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Check the 2-step auth token // Check the 2-step auth token
function checkUserOneTimePassword(req, domain, user, token, hwtoken, func) { function checkUserOneTimePassword(req, domain, user, token, hwtoken, func) {
parent.debug('web', 'checkUserOneTimePassword()');
const twoStepLoginSupported = ((domain.auth != 'sspi') && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.nousers !== true) && (parent.config.settings.no2factorauth !== true)); const twoStepLoginSupported = ((domain.auth != 'sspi') && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.nousers !== true) && (parent.config.settings.no2factorauth !== true));
if (twoStepLoginSupported == false) { func(true); return; }; if (twoStepLoginSupported == false) { parent.debug('web', 'checkUserOneTimePassword: not supported.'); func(true); return; };
// Check hardware key // Check hardware key
if (user.otphkeys && (user.otphkeys.length > 0) && (typeof (hwtoken) == 'string') && (hwtoken.length > 0)) { if (user.otphkeys && (user.otphkeys.length > 0) && (typeof (hwtoken) == 'string') && (hwtoken.length > 0)) {
@ -556,13 +557,15 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
}; };
var webauthnResponse = null; var webauthnResponse = null;
try { webauthnResponse = obj.webauthn.verifyAuthenticatorAssertionResponse(clientAssertionResponse.response, assertionExpectations); } catch (ex) { console.log(ex); } try { webauthnResponse = obj.webauthn.verifyAuthenticatorAssertionResponse(clientAssertionResponse.response, assertionExpectations); } catch (ex) { parent.debug('web', 'checkUserOneTimePassword: exception ' + ex); console.log(ex); }
if ((webauthnResponse != null) && (webauthnResponse.verified === true)) { if ((webauthnResponse != null) && (webauthnResponse.verified === true)) {
// Update the hardware key counter and accept the 2nd factor // Update the hardware key counter and accept the 2nd factor
webAuthnKey.counter = webauthnResponse.counter; webAuthnKey.counter = webauthnResponse.counter;
obj.db.SetUser(user); obj.db.SetUser(user);
parent.debug('web', 'checkUserOneTimePassword: success.');
func(true); func(true);
} else { } else {
parent.debug('web', 'checkUserOneTimePassword: fail.');
func(false); func(false);
} }
return; return;
@ -600,6 +603,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
} }
} }
parent.debug('web', 'checkUserOneTimePassword: fail (2).');
func(false); func(false);
} }
@ -615,10 +619,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
var authnOptions = { type: 'webAuthn', keyIds: [], timeout: 60000, challenge: obj.crypto.randomBytes(64).toString('base64') }; var authnOptions = { type: 'webAuthn', keyIds: [], timeout: 60000, challenge: obj.crypto.randomBytes(64).toString('base64') };
for (var i = 0; i < webAuthnKeys.length; i++) { authnOptions.keyIds.push(webAuthnKeys[i].keyId); } for (var i = 0; i < webAuthnKeys.length; i++) { authnOptions.keyIds.push(webAuthnKeys[i].keyId); }
req.session.u2fchallenge = authnOptions.challenge; req.session.u2fchallenge = authnOptions.challenge;
parent.debug('web', 'getHardwareKeyChallenge: success');
func(JSON.stringify(authnOptions)); func(JSON.stringify(authnOptions));
return; return;
} }
} }
parent.debug('web', 'getHardwareKeyChallenge: fail');
func(''); func('');
} }
@ -757,7 +763,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function handleCreateAccountRequest(req, res) { function handleCreateAccountRequest(req, res) {
const domain = checkUserIpAddress(req, res); const domain = checkUserIpAddress(req, res);
if ((domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap')) { res.sendStatus(404); return; } if ((domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap')) {
parent.debug('web', 'handleCreateAccountRequest: failed checks.');
res.sendStatus(404);
return;
}
// Always lowercase the email address // Always lowercase the email address
if (req.body.email) { req.body.email = req.body.email.toLowerCase(); } if (req.body.email) { req.body.email = req.body.email.toLowerCase(); }
@ -770,7 +780,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if ((domain.newaccounts !== 1) && (domain.newaccounts !== true)) { if ((domain.newaccounts !== 1) && (domain.newaccounts !== true)) {
domainUserCount = 0; domainUserCount = 0;
for (var i in obj.users) { if (obj.users[i].domain == domain.id) { domainUserCount++; } } for (var i in obj.users) { if (obj.users[i].domain == domain.id) { domainUserCount++; } }
if (domainUserCount > 0) { res.sendStatus(401); return; } if (domainUserCount > 0) {
parent.debug('web', 'handleCreateAccountRequest: domainUserCount > 1.');
res.sendStatus(401);
return;
}
} }
// Check if this request is for an allows email domain // Check if this request is for an allows email domain
@ -1051,25 +1065,32 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if ((cookie != null) && (cookie.u != null) && (cookie.e != null)) { if ((cookie != null) && (cookie.u != null) && (cookie.e != null)) {
var idsplit = cookie.u.split('/'); var idsplit = cookie.u.split('/');
if ((idsplit.length != 2) || (idsplit[0] != domain.id)) { if ((idsplit.length != 2) || (idsplit[0] != domain.id)) {
parent.debug('web', 'handleCheckMailRequest: Invalid domain.');
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>.' }); 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>.' });
} else { } else {
obj.db.Get('user/' + cookie.u.toLowerCase(), function (err, docs) { obj.db.Get('user/' + cookie.u.toLowerCase(), function (err, docs) {
if (docs.length == 0) { if (docs.length == 0) {
parent.debug('web', 'handleCheckMailRequest: Invalid username.');
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>.' }); 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>.' });
} else { } else {
var user = docs[0]; var user = docs[0];
if (user.email != cookie.e) { if (user.email != cookie.e) {
parent.debug('web', 'handleCheckMailRequest: Invalid e-mail.');
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>.' }); 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>.' });
} else { } else {
if (cookie.a == 1) { if (cookie.a == 1) {
// Account email verification // Account email verification
if (user.emailVerified == true) { if (user.emailVerified == true) {
parent.debug('web', 'handleCheckMailRequest: email already verified.');
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>.' }); 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>.' });
} else { } else {
obj.db.GetUserWithVerifiedEmail(domain.id, user.email, function (err, docs) { obj.db.GetUserWithVerifiedEmail(domain.id, user.email, function (err, docs) {
if (docs.length > 0) { if (docs.length > 0) {
parent.debug('web', 'handleCheckMailRequest: email already in use.');
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>.' }); 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>.' });
} else { } else {
parent.debug('web', 'handleCheckMailRequest: email verification success.');
// Set the verified flag // Set the verified flag
obj.users[user._id].emailVerified = true; obj.users[user._id].emailVerified = true;
user.emailVerified = true; user.emailVerified = true;
@ -1091,6 +1112,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
} else if (cookie.a == 2) { } else if (cookie.a == 2) {
// Account reset // Account reset
if (user.emailVerified != true) { if (user.emailVerified != true) {
parent.debug('web', 'handleCheckMailRequest: email not verified.');
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>.' }); 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>.' });
} else { } else {
// Set a temporary password // Set a temporary password
@ -1117,6 +1139,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Send the new password // Send the new password
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>.' }); 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>.' });
parent.debug('web', 'handleCheckMailRequest: send temporary password.');
}, 0); }, 0);
}); });
} }
@ -1149,6 +1172,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (mesh == null) { res.sendStatus(404); return; } if (mesh == null) { res.sendStatus(404); return; }
var installflags = cookie.f; var installflags = cookie.f;
if (typeof installflags != 'number') { installflags = 0; } if (typeof installflags != 'number') { installflags = 0; }
parent.debug('web', 'handleAgentInviteRequest using cookie.');
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 }); 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 });
} else if (req.query.m != null) { } else if (req.query.m != null) {
// The MeshId is specified in the query string, use that // The MeshId is specified in the query string, use that
@ -1157,6 +1181,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
var installflags = 0; var installflags = 0;
if (req.query.f) { installflags = parseInt(req.query.f); } if (req.query.f) { installflags = parseInt(req.query.f); }
if (typeof installflags != 'number') { installflags = 0; } if (typeof installflags != 'number') { installflags = 0; }
parent.debug('web', 'handleAgentInviteRequest using meshid.');
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 }); 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 });
} }
} }
@ -1172,7 +1197,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Check if the user is logged and we have all required parameters // Check if the user is logged and we have all required parameters
if (!req.session || !req.session.userid || !req.body.apassword1 || (req.body.apassword1 != req.body.apassword2) || (req.session.domainid != domain.id)) { res.redirect(domain.url + getQueryPortion(req)); return; } if (!req.session || !req.session.userid || !req.body.apassword1 || (req.body.apassword1 != req.body.apassword2) || (req.session.domainid != domain.id)) { res.redirect(domain.url + getQueryPortion(req)); return; }
var user = obj.users[req.session.userid]; var user = obj.users[req.session.userid];
if (!user) return; if (!user) { parent.debug('web', 'handleDeleteAccountRequest: user not found.'); res.sendStatus(404); return; }
// Check if the password is correct // Check if the password is correct
obj.authenticate(user.name, req.body.apassword1, domain, function (err, userid) { obj.authenticate(user.name, req.body.apassword1, domain, function (err, userid) {
@ -1202,7 +1227,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
req.session = null; req.session = null;
res.redirect(domain.url + getQueryPortion(req)); res.redirect(domain.url + getQueryPortion(req));
obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', userid: user._id, username: user.name, action: 'accountremove', msg: 'Account removed', domain: domain.id }); obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', userid: user._id, username: user.name, action: 'accountremove', msg: 'Account removed', domain: domain.id });
parent.debug('web', 'handleDeleteAccountRequest: removed user.');
} else { } else {
parent.debug('web', 'handleDeleteAccountRequest: auth failed.');
res.redirect(domain.url + getQueryPortion(req)); res.redirect(domain.url + getQueryPortion(req));
} }
}); });
@ -1214,9 +1241,10 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (user.passtype != null) { if (user.passtype != null) {
// IIS default clear or weak password hashing (SHA-1) // IIS default clear or weak password hashing (SHA-1)
require('./pass').iishash(user.passtype, password, user.salt, function (err, hash) { require('./pass').iishash(user.passtype, password, user.salt, function (err, hash) {
if (err) return func(false); if (err) { parent.debug('web', 'checkUserPassword: SHA-1 fail.'); return func(false); }
if (hash == user.hash) { if (hash == user.hash) {
if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { return func(false); } // Account is locked if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { parent.debug('web', 'checkUserPassword: SHA-1 locked.'); return func(false); } // Account is locked
parent.debug('web', 'checkUserPassword: SHA-1 ok.');
return func(true); // Allow password change return func(true); // Allow password change
} }
func(false); func(false);
@ -1224,9 +1252,10 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
} else { } else {
// Default strong password hashing (pbkdf2 SHA384) // Default strong password hashing (pbkdf2 SHA384)
require('./pass').hash(password, user.salt, function (err, hash, tag) { require('./pass').hash(password, user.salt, function (err, hash, tag) {
if (err) return func(false); if (err) { parent.debug('web', 'checkUserPassword: pbkdf2 SHA384 fail.'); return func(false); }
if (hash == user.hash) { if (hash == user.hash) {
if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { return func(false); } // Account is locked if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { parent.debug('web', 'checkUserPassword: pbkdf2 SHA384 locked.'); return func(false); } // Account is locked
parent.debug('web', 'checkUserPassword: pbkdf2 SHA384 ok.');
return func(true); // Allow password change return func(true); // Allow password change
} }
func(false); func(false);
@ -1238,24 +1267,30 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function handlePasswordChangeRequest(req, res) { function handlePasswordChangeRequest(req, res) {
const domain = checkUserIpAddress(req, res); const domain = checkUserIpAddress(req, res);
if ((domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap')) { if ((domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap')) {
parent.debug('web', 'handlePasswordChangeRequest: failed checks.'); parent.debug('web', 'handlePasswordChangeRequest: failed checks (1).');
res.sendStatus(404); res.sendStatus(404);
return; return;
} }
// Check if the user is logged and we have all required parameters // Check if the user is logged and we have all required parameters
if (!req.session || !req.session.userid || !req.body.apassword0 || !req.body.apassword1 || (req.body.apassword1 != req.body.apassword2) || (req.session.domainid != domain.id)) { res.redirect(domain.url + getQueryPortion(req)); return; } if (!req.session || !req.session.userid || !req.body.apassword0 || !req.body.apassword1 || (req.body.apassword1 != req.body.apassword2) || (req.session.domainid != domain.id)) {
parent.debug('web', 'handlePasswordChangeRequest: failed checks (2).');
res.redirect(domain.url + getQueryPortion(req)); return;
}
// Get the current user // Get the current user
var user = obj.users[req.session.userid]; var user = obj.users[req.session.userid];
if (!user) { res.redirect(domain.url + getQueryPortion(req)); return; } if (!user) {
parent.debug('web', 'handlePasswordChangeRequest: user not found.');
res.redirect(domain.url + getQueryPortion(req)); return;
}
// Check old password // Check old password
obj.checkUserPassword(domain, user, req.body.apassword0, function (result) { obj.checkUserPassword(domain, user, req.body.apassword0, function (result) {
if (result == true) { if (result == true) {
// Update the password // Update the password
require('./pass').hash(req.body.apassword1, function (err, salt, hash, tag) { require('./pass').hash(req.body.apassword1, function (err, salt, hash, tag) {
if (err) throw err; if (err) { parent.debug('web', 'handlePasswordChangeRequest: hash error.'); throw err; }
user.salt = salt; user.salt = salt;
user.hash = hash; user.hash = hash;
if ((domain.passwordrequirements != null) && (domain.passwordrequirements.hint === true) && (req.body.apasswordhint)) { var hint = req.body.apasswordhint; if (hint.length > 250) hint = hint.substring(0, 250); user.passhint = hint; } else { delete user.passhint; } if ((domain.passwordrequirements != null) && (domain.passwordrequirements.hint === true) && (req.body.apasswordhint)) { var hint = req.body.apasswordhint; if (hint.length > 250) hint = hint.substring(0, 250); user.passhint = hint; } else { delete user.passhint; }
@ -1278,10 +1313,19 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if ((domain.sspi != null) && ((req.query.login == null) || (obj.parent.loginCookieEncryptionKey == null))) { if ((domain.sspi != null) && ((req.query.login == null) || (obj.parent.loginCookieEncryptionKey == null))) {
// Login using SSPI // Login using SSPI
domain.sspi.authenticate(req, res, function (err) { if ((err != null) || (req.connection.user == null)) { res.end('Authentication Required...'); } else { handleRootRequestEx(req, res, domain); } }); domain.sspi.authenticate(req, res, function (err) {
if ((err != null) || (req.connection.user == null)) {
parent.debug('web', 'handleRootRequest: SSPI auth required.');
res.end('Authentication Required...');
} else {
parent.debug('web', 'handleRootRequest: SSPI auth ok.');
handleRootRequestEx(req, res, domain);
}
});
} else if (req.query.user && req.query.pass) { } else if (req.query.user && req.query.pass) {
// User credentials are being passed in the URL. WARNING: Putting credentials in a URL is not good security... but people are requesting this option. // User credentials are being passed in the URL. WARNING: Putting credentials in a URL is not good security... but people are requesting this option.
obj.authenticate(req.query.user, req.query.pass, domain, function (err, userid) { obj.authenticate(req.query.user, req.query.pass, domain, function (err, userid) {
parent.debug('web', 'handleRootRequest: user/pass in URL auth ok.');
req.session.userid = userid; req.session.userid = userid;
req.session.domainid = domain.id; req.session.domainid = domain.id;
req.session.currentNode = ''; req.session.currentNode = '';
@ -1298,7 +1342,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' }); res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' });
// Check if we have an incomplete domain name in the path // Check if we have an incomplete domain name in the path
if ((domain.id != '') && (domain.dns == null) && (req.url.split('/').length == 2)) { res.redirect(domain.url + getQueryPortion(req)); return; } if ((domain.id != '') && (domain.dns == null) && (req.url.split('/').length == 2)) {
parent.debug('web', 'handleRootRequestEx: incomplete domain name in the path.');
res.redirect(domain.url + getQueryPortion(req));
return;
}
if (obj.args.nousers == true) { if (obj.args.nousers == true) {
// If in single user mode, setup things here. // If in single user mode, setup things here.
@ -1308,11 +1356,13 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
req.session.currentNode = ''; req.session.currentNode = '';
if (obj.users[req.session.userid] == null) { if (obj.users[req.session.userid] == null) {
// Create the dummy user ~ with impossible password // Create the dummy user ~ with impossible password
parent.debug('web', 'handleRootRequestEx: created dummy user in nouser mode.');
obj.users[req.session.userid] = { type: 'user', _id: req.session.userid, name: '~', email: '~', domain: domain.id, siteadmin: 4294967295 }; obj.users[req.session.userid] = { type: 'user', _id: req.session.userid, name: '~', email: '~', domain: domain.id, siteadmin: 4294967295 };
obj.db.SetUser(obj.users[req.session.userid]); obj.db.SetUser(obj.users[req.session.userid]);
} }
} else if (obj.args.user && obj.users['user/' + domain.id + '/' + obj.args.user.toLowerCase()]) { } else if (obj.args.user && obj.users['user/' + domain.id + '/' + obj.args.user.toLowerCase()]) {
// If a default user is active, setup the session here. // If a default user is active, setup the session here.
parent.debug('web', 'handleRootRequestEx: auth using default user.');
if (req.session && req.session.loginmode) { delete req.session.loginmode; } if (req.session && req.session.loginmode) { delete req.session.loginmode; }
req.session.userid = 'user/' + domain.id + '/' + obj.args.user.toLowerCase(); req.session.userid = 'user/' + domain.id + '/' + obj.args.user.toLowerCase();
req.session.domainid = domain.id; req.session.domainid = domain.id;
@ -1321,15 +1371,19 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
var loginCookie = obj.parent.decodeCookie(req.query.login, obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout var loginCookie = obj.parent.decodeCookie(req.query.login, obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
if ((loginCookie != null) && (loginCookie.a == 3) && (loginCookie.u != null) && (loginCookie.u.split('/')[1] == domain.id)) { if ((loginCookie != null) && (loginCookie.a == 3) && (loginCookie.u != null) && (loginCookie.u.split('/')[1] == domain.id)) {
// If a login cookie was provided, setup the session here. // If a login cookie was provided, setup the session here.
parent.debug('web', 'handleRootRequestEx: cookie auth ok.');
if (req.session && req.session.loginmode) { delete req.session.loginmode; } if (req.session && req.session.loginmode) { delete req.session.loginmode; }
req.session.userid = loginCookie.u; req.session.userid = loginCookie.u;
req.session.domainid = domain.id; req.session.domainid = domain.id;
req.session.currentNode = ''; req.session.currentNode = '';
} else {
parent.debug('web', 'handleRootRequestEx: cookie auth failed.');
} }
} else if (domain.sspi != null) { } else if (domain.sspi != null) {
// SSPI login (Windows only) // SSPI login (Windows only)
//console.log(req.connection.user, req.connection.userSid); //console.log(req.connection.user, req.connection.userSid);
if ((req.connection.user == null) || (req.connection.userSid == null)) { if ((req.connection.user == null) || (req.connection.userSid == null)) {
parent.debug('web', 'handleRootRequestEx: SSPI no user auth.');
res.sendStatus(404); return; res.sendStatus(404); return;
} else { } else {
nologout = true; nologout = true;
@ -1352,6 +1406,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
var event = { etype: 'user', userid: req.session.userid, username: req.connection.user, account: obj.CloneSafeUser(user2), action: 'accountcreate', msg: 'Domain account created, user ' + req.connection.user, domain: domain.id }; var event = { etype: 'user', userid: req.session.userid, username: req.connection.user, account: obj.CloneSafeUser(user2), action: 'accountcreate', msg: 'Domain account created, user ' + req.connection.user, domain: domain.id };
if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to create the user. Another event will come. if (obj.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to create the user. Another event will come.
obj.parent.DispatchEvent(['*', 'server-users'], obj, event); obj.parent.DispatchEvent(['*', 'server-users'], obj, event);
parent.debug('web', 'handleRootRequestEx: SSPI new domain user.');
} }
} }
} }
@ -1366,11 +1421,17 @@ 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 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]) { if (req.session && req.session.userid && obj.users[req.session.userid]) {
var user = obj.users[req.session.userid]; var user = obj.users[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) { // Check is the session is for the correct domain
parent.debug('web', 'handleRootRequestEx: incorrect domain.');
req.session = null;
res.redirect(domain.url + getQueryPortion(req));
return;
}
// Check if this is a locked account // Check if this is a locked account
if ((user.siteadmin != null) && ((user.siteadmin & 32) != 0) && (user.siteadmin != 0xFFFFFFFF)) { if ((user.siteadmin != null) && ((user.siteadmin & 32) != 0) && (user.siteadmin != 0xFFFFFFFF)) {
// Locked account // Locked account
parent.debug('web', 'handleRootRequestEx: locked account.');
delete req.session.userid; delete req.session.userid;
delete req.session.domainid; delete req.session.domainid;
delete req.session.currentNode; delete req.session.currentNode;
@ -1430,6 +1491,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
// Fetch the web state // Fetch the web state
parent.debug('web', 'handleRootRequestEx: success.');
obj.db.Get('ws' + user._id, function (err, states) { obj.db.Get('ws' + user._id, function (err, states) {
var webstate = (states.length == 1) ? states[0].state : ''; var webstate = (states.length == 1) ? states[0].state : '';
if (obj.args.minify && !req.query.nominify) { if (obj.args.minify && !req.query.nominify) {
@ -1452,6 +1514,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if ((req.session.loginmode == '4') && (req.session.tokenusername)) { if ((req.session.loginmode == '4') && (req.session.tokenusername)) {
var user = obj.users['user/' + domain.id + '/' + req.session.tokenusername.toLowerCase()]; var user = obj.users['user/' + domain.id + '/' + req.session.tokenusername.toLowerCase()];
if (user != null) { if (user != null) {
parent.debug('web', 'handleRootRequestEx: sending 2FA challenge.');
getHardwareKeyChallenge(req, domain, user, function (hwchallenge) { handleRootRequestLogin(req, res, domain, hwchallenge, passRequirements); }); getHardwareKeyChallenge(req, domain, user, function (hwchallenge) { handleRootRequestLogin(req, res, domain, hwchallenge, passRequirements); });
return; return;
} }
@ -1460,13 +1523,16 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if ((req.session.loginmode == '5') && (req.session.tokenemail)) { if ((req.session.loginmode == '5') && (req.session.tokenemail)) {
obj.db.GetUserWithVerifiedEmail(domain.id, req.session.tokenemail, function (err, docs) { obj.db.GetUserWithVerifiedEmail(domain.id, req.session.tokenemail, function (err, docs) {
if ((err != null) || (docs.length == 0)) { if ((err != null) || (docs.length == 0)) {
parent.debug('web', 'handleRootRequestEx: password recover 2FA fail.');
req.session = null; req.session = null;
res.redirect(domain.url + getQueryPortion(req)); res.redirect(domain.url + getQueryPortion(req));
} else { } else {
var user = obj.users[docs[0]._id]; var user = obj.users[docs[0]._id];
if (user != null) { if (user != null) {
parent.debug('web', 'handleRootRequestEx: password recover 2FA challenge.');
getHardwareKeyChallenge(req, domain, user, function (hwchallenge) { handleRootRequestLogin(req, res, domain, hwchallenge, passRequirements); }); getHardwareKeyChallenge(req, domain, user, function (hwchallenge) { handleRootRequestLogin(req, res, domain, hwchallenge, passRequirements); });
} else { } else {
parent.debug('web', 'handleRootRequestEx: password recover 2FA no user.');
req.session = null; req.session = null;
res.redirect(domain.url + getQueryPortion(req)); res.redirect(domain.url + getQueryPortion(req));
} }
@ -1479,6 +1545,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
} }
function handleRootRequestLogin(req, res, domain, hardwareKeyChallenge, passRequirements) { function handleRootRequestLogin(req, res, domain, hardwareKeyChallenge, passRequirements) {
parent.debug('web', 'handleRootRequestLogin()');
var features = 0; var features = 0;
if ((parent.config != null) && (parent.config.settings != null) && (parent.config.settings.allowframing == true)) { features += 32; } // Allow site within iframe if ((parent.config != null) && (parent.config.settings != null) && (parent.config.settings.allowframing == true)) { features += 32; } // Allow site within iframe
if (domain.usernameisemail) { features += 0x00200000; } // Username is email address if (domain.usernameisemail) { features += 0x00200000; } // Username is email address
@ -1550,7 +1617,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Render the terms of service. // Render the terms of service.
function handleTermsRequest(req, res) { function handleTermsRequest(req, res) {
const domain = checkUserIpAddress(req, res); const domain = checkUserIpAddress(req, res);
if (domain == null) { res.sendStatus(404); return; } if (domain == null) {
parent.debug('web', 'handleTermsRequest: Bad domain');
res.sendStatus(404);
return;
}
// See if term.txt was loaded from the database // See if term.txt was loaded from the database
if ((parent.configurationFiles != null) && (parent.configurationFiles['terms.txt'] != null)) { if ((parent.configurationFiles != null) && (parent.configurationFiles['terms.txt'] != null)) {
@ -1570,7 +1641,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
var p = obj.path.join(obj.parent.datapath, 'terms.txt'); var p = obj.path.join(obj.parent.datapath, 'terms.txt');
if (obj.fs.existsSync(p)) { if (obj.fs.existsSync(p)) {
obj.fs.readFile(p, 'utf8', function (err, data) { obj.fs.readFile(p, 'utf8', function (err, data) {
if (err != null) { res.sendStatus(404); return; } if (err != null) { parent.debug('web', 'handleTermsRequest: no terms.txt'); res.sendStatus(404); return; }
// Send the terms from terms.txt // Send the terms from terms.txt
res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' }); res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' });
@ -1586,6 +1657,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
}); });
} else { } else {
// Send the default terms // Send the default terms
parent.debug('web', 'handleTermsRequest: sending default terms');
res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' }); res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' });
if (req.session && req.session.userid) { 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 is the session is for the correct domain
@ -1603,7 +1675,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Render the messenger application. // Render the messenger application.
function handleMessengerRequest(req, res) { function handleMessengerRequest(req, res) {
const domain = checkUserIpAddress(req, res); const domain = checkUserIpAddress(req, res);
if (domain == null) { res.sendStatus(404); return; } if (domain == null) { parent.debug('web', 'handleMessengerRequest: no domain'); res.sendStatus(404); return; }
parent.debug('web', 'handleMessengerRequest()');
var webRtcConfig = null; 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)); } 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)); }
@ -1623,7 +1696,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Returns the mesh server root certificate // Returns the mesh server root certificate
function handleRootCertRequest(req, res) { function handleRootCertRequest(req, res) {
if ((obj.userAllowedIp != null) && (checkIpAddressEx(req, res, obj.userAllowedIp, false) === false)) { return; } // Check server-wide IP filter only. if ((obj.userAllowedIp != null) && (checkIpAddressEx(req, res, obj.userAllowedIp, false) === false)) { parent.debug('web', 'handleRootCertRequest: invalid ip'); return; } // Check server-wide IP filter only.
parent.debug('web', 'handleRootCertRequest()');
try { try {
res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0', 'Content-Type': 'application/octet-stream', 'Content-Disposition': 'attachment; filename="' + certificates.RootName + '.cer"' }); res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0', 'Content-Type': 'application/octet-stream', 'Content-Disposition': 'attachment; filename="' + certificates.RootName + '.cer"' });
} catch (ex) { } catch (ex) {
@ -3187,7 +3261,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
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); }); 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); });
// Indicates to ExpressJS that the public folder should be used to serve static files. // Indicates to ExpressJS that the public folder should be used to serve static files.
obj.app.use(url, obj.express.static(obj.parent.webPublicPath)); obj.app.use(url, obj.express.static(obj.parent.webPublicPath, { maxAge: '1h' }));
// Start regular disconnection list flush every 2 minutes. // Start regular disconnection list flush every 2 minutes.
obj.wsagentsDisconnectionsTimer = setInterval(function () { obj.wsagentsDisconnections = {}; }, 120000); obj.wsagentsDisconnectionsTimer = setInterval(function () { obj.wsagentsDisconnections = {}; }, 120000);