diff --git a/meshcentral.js b/meshcentral.js index dcff8ab8..eae34229 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -79,7 +79,7 @@ function CreateMeshCentralServer(config, args) { try { require('./pass').hash('test', function () { }); } catch (e) { console.log('Old version of node, must upgrade.'); return; } // TODO: Not sure if this test works or not. // Check for invalid arguments - var validArguments = ['_', 'notls', 'user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'cert', 'mpscert', 'deletedomain', 'deletedefaultdomain', 'showall', 'showusers', 'shownodes', 'showmeshes', 'showevents', 'showpower', 'clearpower', 'showiplocations', 'help', 'exactports', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpsdebug', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbimport', 'selfupdate', 'tlsoffload', 'userallowedip', 'fastcert', 'swarmport', 'swarmdebug', 'logintoken', 'logintokenkey', 'logintokengen', 'logintokengen', 'mailtokengen', 'admin', 'unadmin']; + var validArguments = ['_', 'notls', 'user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'cert', 'mpscert', 'deletedomain', 'deletedefaultdomain', 'showall', 'showusers', 'shownodes', 'showmeshes', 'showevents', 'showpower', 'clearpower', 'showiplocations', 'help', 'exactports', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpsdebug', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbimport', 'selfupdate', 'tlsoffload', 'userallowedip', 'fastcert', 'swarmport', 'swarmdebug', 'logintoken', 'logintokenkey', 'logintokengen', 'logintokengen', 'mailtokengen', 'admin', 'unadmin', 'sessionkey', 'sessiontime']; for (var arg in obj.args) { obj.args[arg.toLocaleLowerCase()] = obj.args[arg]; if (validArguments.indexOf(arg.toLocaleLowerCase()) == -1) { console.log('Invalid argument "' + arg + '", use --help.'); return; } } if (obj.args.mongodb == true) { console.log('Must specify: --mongodb [connectionstring] \r\nSee https://docs.mongodb.com/manual/reference/connection-string/ for MongoDB connection string.'); return; } for (var i in obj.config.settings) { obj.args[i] = obj.config.settings[i]; } // Place all settings into arguments, arguments have already been placed into settings so arguments take precedence. @@ -419,13 +419,12 @@ function CreateMeshCentralServer(config, args) { // If the server is set to "nousers", allow only loopback unless IP filter is set if ((obj.args.nousers == true) && (obj.args.userallowedip == null)) { obj.args.userallowedip = "::1,127.0.0.1"; } - if (obj.args.secret) { - // This secret is used to encrypt HTTP session information, if specified, user it. - obj.webserver = require('./webserver.js').CreateWebServer(obj, obj.db, obj.args, obj.args.secret, obj.certificates); - } else { - // If the secret is not specified, generate a random number. - obj.webserver = require('./webserver.js').CreateWebServer(obj, obj.db, obj.args, buf.toString('hex').toUpperCase(), obj.certificates); - } + // Set the session length to 60 minutes if not set and set a random key if needed + if ((obj.args.sessiontime == null) || (typeof obj.args.sessiontime != 'number') || (obj.args.sessiontime < 1)) { obj.args.sessiontime = 60; } + if (!obj.args.sessionkey) { obj.args.sessionkey = buf.toString('hex').toUpperCase(); } + + // Start eh web server and if needed, the redirection web server. + obj.webserver = require('./webserver.js').CreateWebServer(obj, obj.db, obj.args, obj.certificates); if (obj.redirserver != null) { obj.redirserver.hookMainWebServer(obj.certificates); } // Setup the Intel AMT event handler diff --git a/package.json b/package.json index b7f74bf3..2575bfaf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.1.9-k", + "version": "0.1.9-m", "keywords": [ "Remote Management", "Intel AMT", @@ -30,6 +30,7 @@ "body-parser": "^1.18.2", "compression": "^1.7.1", "connect-redis": "^3.3.3", + "cookie-session": "^2.0.0-beta.3", "express": "^4.16.2", "express-handlebars": "^3.0.0", "express-session": "^1.15.6", @@ -43,7 +44,7 @@ "xmldom": "^0.1.27", "yauzl": "^2.9.1" }, - "devDependencies": { }, + "devDependencies": {}, "repository": { "type": "git", "url": "https://github.com/Ylianst/MeshCentral.git" diff --git a/sample-config.json b/sample-config.json index fa94978d..77fae33b 100644 --- a/sample-config.json +++ b/sample-config.json @@ -5,6 +5,8 @@ "_MongoDbCol": "meshcentral", "_WANonly": true, "_LANonly": true, + "_SessionTime": 30, + "_SessionKey": "MyReallySecretPassword", "_Port": 443, "_RedirPort": 80, "_AllowLoginToken": true, diff --git a/views/default.handlebars b/views/default.handlebars index dd616097..7d6da388 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -5174,7 +5174,7 @@ if (maxUsers > 0) { if (addHeader) { x += 'Online Users'; addHeader = false; } x += addUserHtml(user, sessions); - maxUsers--; + maxUsers--; } else { hiddenUsers++; } diff --git a/views/login.handlebars b/views/login.handlebars index 510cdad4..a5083461 100644 --- a/views/login.handlebars +++ b/views/login.handlebars @@ -72,27 +72,27 @@ - + - + - + - + - + - + @@ -224,8 +224,20 @@ function validateCreate(box,e) { setDialogMode(0); - var ok = ((Q('ausername').value.length > 0) && (Q('ausername').value.indexOf(' ') == -1) && (validateEmail(Q('aemail').value) == true) && (Q('apassword1').value.length > 0) && (Q('apassword2').value == Q('apassword1').value)); - if ((newAccountPass == 1) && (Q('anewaccountpass').value.length == 0)) { ok = false; } + var userok = (Q('ausername').value.length > 0) && (Q('ausername').value.indexOf(' ') == -1); + var emailok = (validateEmail(Q('aemail').value) == true); + var pass1ok = (Q('apassword1').value.length > 0); + var pass2ok = (Q('apassword2').value.length > 0) && (Q('apassword2').value == Q('apassword1').value); + var newAccOk = (newAccountPass == 0) || (Q('anewaccountpass').value.length > 0); + var ok = (userok && emailok && pass1ok && pass2ok && newAccOk); + + // Color the fields + QS('nuUser').color = userok?'black':'#7b241c'; + QS('nuEmail').color = emailok?'black':'#7b241c'; + QS('nuPass1').color = pass1ok?'black':'#7b241c'; + QS('nuPass2').color = pass2ok?'black':'#7b241c'; + QS('nuToken').color = newAccOk?'black':'#7b241c'; + QE('createButton', ok); if (Q('apassword1').value == '') { QH('passWarning', ''); diff --git a/webserver.js b/webserver.js index 3df919d0..ffd953c8 100644 --- a/webserver.js +++ b/webserver.js @@ -36,7 +36,7 @@ if (!String.prototype.startsWith) { String.prototype.startsWith = function (sear if (!String.prototype.endsWith) { String.prototype.endsWith = function (searchString, position) { var subjectString = this.toString(); if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) { position = subjectString.length; } position -= searchString.length; var lastIndex = subjectString.lastIndexOf(searchString, position); return lastIndex !== -1 && lastIndex === position; }; } // Construct a HTTP web server object -module.exports.CreateWebServer = function (parent, db, args, secret, certificates) { +module.exports.CreateWebServer = function (parent, db, args, certificates) { var obj = {}; // Modules @@ -46,7 +46,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate obj.path = require('path'); obj.constants = require('constants'); obj.bodyParser = require('body-parser'); - obj.session = require('express-session'); + obj.session = require('cookie-session'); obj.exphbs = require('express-handlebars'); obj.crypto = require('crypto'); obj.common = require('./common.js'); @@ -154,9 +154,11 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate obj.app.set('view engine', 'handlebars'); obj.app.use(obj.bodyParser.urlencoded({ extended: false })); obj.app.use(obj.session({ - resave: false, // don't save session if unmodified - saveUninitialized: false, // don't create session until something stored - secret: secret // If multiple instances of this server are behind a load-balancer, this secret must be the same for all instances + name: 'xid', // Recommanded security practice to not use the default cookie name + httpOnly: true, + keys: [ obj.args.sessionkey ], // If multiple instances of this server are behind a load-balancer, this secret must be the same for all instances + secure: (obj.args.notls != true), // Use this cookie only over TLS + maxAge: (obj.args.sessiontime * 60 * 1000) // 24 hours })); // Session-persisted message middleware @@ -290,9 +292,8 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate var user = obj.users[req.session.userid] obj.parent.DispatchEvent(['*'], obj, { etype: 'user', username: user.name, action: 'logout', msg: 'Account logout', domain: domain.id }) } - req.session.destroy(function () { - res.redirect(domain.url); - }); + req.session = null; + res.redirect(domain.url); } function handleLoginRequest(req, res) { @@ -306,8 +307,9 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate user.login = Date.now(); obj.db.SetUser(user); + // Regenerate session when signing in to prevent fixation - req.session.regenerate(function () { + //req.session.regenerate(function () { // Store the user's primary key in the session store to be retrieved, or in this case the entire user object // req.session.success = 'Authenticated as ' + user.name + 'click to logout. You may now access /restricted.'; delete req.session.loginmode; @@ -334,7 +336,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate } else { res.redirect(domain.url); } - }); + //}); obj.parent.DispatchEvent(['*'], obj, { etype: 'user', username: user.name, action: 'login', msg: 'Account login', domain: domain.id }) } else { @@ -569,7 +571,8 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate // Remove the user obj.db.Remove(user._id); delete obj.users[user._id]; - req.session.destroy(function () { res.redirect(domain.url); }); + req.session = null; + res.redirect(domain.url); obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', username: user.name, action: 'accountremove', msg: 'Account removed', domain: domain.id }) } else { res.redirect(domain.url); @@ -679,7 +682,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate // If a user is logged in, serve the default app, otherwise server the login app. if (req.session && req.session.userid) { - if (req.session.domainid != domain.id) { req.session.destroy(function () { res.redirect(domain.url); }); return; } // Check is the session is for the correct domain + if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url); return; } // Check is the session is for the correct domain var viewmode = 1; if (req.session.viewmode) { viewmode = req.session.viewmode; @@ -751,7 +754,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate if (domain == null) return; 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.destroy(function () { res.redirect(domain.url); }); return; } // Check is the session is for the correct domain + if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url); return; } // Check is the session is for the correct domain var user = obj.users[req.session.userid]; res.render(obj.path.join(__dirname, isMobileBrowser(req) ? 'views/terms-mobile' : 'views/terms'), { title: domain.title, title2: domain.title2, logoutControl: 'Welcome ' + user.name + '. Logout' }); } else {
Username:Username:
Email:Email:
Password:Password:
Password:Password:
Password Hint:Password Hint:
Creation Token:Creation Token: