Added server password timeout and reset on next login.

This commit is contained in:
Ylian Saint-Hilaire 2019-02-27 18:48:50 -08:00
parent 5a4f0e6450
commit 8709d56bd0
14 changed files with 377 additions and 48 deletions

View File

@ -316,6 +316,7 @@ function run(argv) {
// Display Intel AMT versions
var amtMeiModule = require('amt-mei');
var amtMei = new amtMeiModule();
if (amtMei == null) { console.log("Intel(R) AMT not supported or insufficient access rights."); exit(1); return; }
amtMei.on('error', function (e) { console.log('ERROR: ' + e); exit(1); return; });
amtMei.getVersion(function (val) {
console.log("MEI Version = " + val.BiosVersion.toString());
@ -326,6 +327,7 @@ function run(argv) {
// Display Intel AMT list of trusted hashes
var amtMeiModule = require('amt-mei');
var amtMei = new amtMeiModule();
if (amtMei == null) { console.log("Intel(R) AMT not supported or insufficient access rights."); exit(1); return; }
amtMei.on('error', function (e) { console.log('ERROR: ' + e); exit(1); return; });
amtMei.getHashHandles(function (handles) {
exitOnCount = handles.length;
@ -341,6 +343,7 @@ function run(argv) {
mestate = {};
var amtMeiModule = require('amt-mei');
var amtMei = new amtMeiModule();
if (amtMei == null) { console.log("Intel(R) AMT not supported or insufficient access rights."); exit(1); return; }
amtMei.on('error', function (e) { console.log('ERROR: ' + e); exit(1); return; });
amtMei.getVersion(function (result) { if (result) { for (var version in result.Versions) { if (result.Versions[version].Description == 'AMT') { mestate.ver = result.Versions[version].Version; } } } });
amtMei.getProvisioningState(function (result) { if (result) { mestate.ProvisioningState = result; } });
@ -381,6 +384,7 @@ function run(argv) {
mestate = {};
var amtMeiModule = require('amt-mei');
var amtMei = new amtMeiModule();
if (amtMei == null) { console.log("Intel(R) AMT not supported or insufficient access rights."); exit(1); return; }
amtMei.on('error', function (e) { console.log('ERROR: ' + e); exit(1); return; });
amtMei.getVersion(function (result) { console.log('getVersion: ' + JSON.stringify(result)); });
amtMei.getProvisioningState(function (result) { console.log('getProvisioningState: ' + JSON.stringify(result)); });
@ -666,6 +670,7 @@ function startMeshCommander() {
function deactivateCCM() {
var amtMeiModule = require('amt-mei');
var amtMei = new amtMeiModule();
if (amtMei == null) { console.log("Intel(R) AMT not supported or insufficient access rights."); exit(1); return; }
amtMei.on('error', function (e) { console.log('ERROR: ' + e); exit(1); return; });
amtMei.unprovision(1, function (status) { if (status == 0) { console.log('Success'); } else { console.log('Error ' + status); } exit(1); });
}
@ -708,6 +713,7 @@ function getAmtUuid() {
if (settings.hostname == null) {
var amtMeiModule = require('amt-mei');
var amtMei = new amtMeiModule();
if (amtMei == null) { console.log("Intel(R) AMT not supported or insufficient access rights."); exit(1); return; }
amtMei.on('error', function (e) { console.log('ERROR: ' + e); exit(1); return; });
amtMei.getUuid(function (result) { if ((result == null) || (result.uuid == null)) { console.log('Failed.'); } else { console.log(result.uuid); } exit(1); });
} else {
@ -829,6 +835,7 @@ function getAmtInfo(func, tag) {
getAmtInfoFetchingTimer = null;
var amtMeiModule = require('amt-mei');
amtMei = new amtMeiModule();
if (amtMei == null) { console.log("Intel(R) AMT not supported or insufficient access rights."); exit(1); return; }
amtMei.on('error', function (e) { console.log('ERROR: ' + e); exit(1); return; });
}, 3000);
amtMei.getProtocolVersion(function (result) { if (result != null) { amtMeiTmpState.MeiVersion = result; } });
@ -890,6 +897,7 @@ function startLms(func) {
var amtMeiModule = require('amt-mei');
amtMei = new amtMeiModule();
if (amtMei == null) { console.log("Intel(R) AMT not supported or insufficient access rights."); exit(1); return; }
amtMei.on('error', function (e) { console.log('ERROR: ' + e); exit(1); return; });
//console.log("PTHI Connected.");

View File

@ -58,7 +58,7 @@ CheckInstallAgent() {
# Linux x86, 64 bit
machineid=6
fi
if [ $machinetype == 'x86' ] || [ $machinetype == 'i686' ]
if [ $machinetype == 'x86' ] || [ $machinetype == 'i686' ] || [ $machinetype == 'i586' ]
then
# Linux x86, 32 bit
machineid=5

View File

@ -470,7 +470,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
switch (cmd) {
case 'help': {
r = 'Available commands: help, args, resetserver, showconfig, usersessions, tasklimiter, setmaxtasks, cores.';
r = 'Available commands: help, args, resetserver, showconfig, usersessions, tasklimiter, setmaxtasks, cores, migrationagents, swarmstats.';
break;
}
case 'args': {
@ -536,6 +536,31 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
r = JSON.stringify(removeAllUnderScore(config), null, 4);
break;
}
case 'migrationagents': {
if (obj.parent.parent.swarmserver == null) {
r = 'Swarm server not running.';
} else {
for (var i in obj.parent.parent.swarmserver.migrationAgents) {
var arch = obj.parent.parent.swarmserver.migrationAgents[i];
for (var j in arch) { var agent = arch[j]; r += 'Arch ' + agent.arch + ', Ver ' + agent.ver + ', Size ' + agent.binary.length + '<br />'; }
}
}
break;
}
case 'swarmstats': {
if (obj.parent.parent.swarmserver == null) {
r = 'Swarm server not running.';
} else {
for (var i in obj.parent.parent.swarmserver.stats) {
if (typeof obj.parent.parent.swarmserver.stats[i] == 'object') {
r += i + ' ' + JSON.stringify(obj.parent.parent.swarmserver.stats[i]) + '<br />';
} else {
r += i + ' ' + obj.parent.parent.swarmserver.stats[i] + '<br />';
}
}
}
break;
}
default: { // This is an unknown command, return an error message
r = 'Unknown command \"' + cmd + '\", type \"help\" for list of avaialble commands.';
break;
@ -822,11 +847,11 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if (hint.length > 250) hint = hint.substring(0, 250);
user.salt = salt;
user.hash = hash;
user.passhint = req.body.apasswordhint;
user.passhint = hint;
user.passchange = Math.floor(Date.now() / 1000);
delete user.passtype;
obj.db.SetUser(user);
obj.parent.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', username: user.name, action: 'passchange', msg: 'Account password changed: ' + user.name, domain: domain.id });
obj.parent.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', username: user.name, account: obj.parent.CloneSafeUser(user), action: 'accountchange', msg: 'Account password changed: ' + user.name, domain: domain.id });
// Send user notification of password change
displayNotificationMessage('Password changed.');
@ -854,19 +879,18 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
// Compute the password hash & save it
require('./pass').hash(command.pass, function (err, salt, hash) {
if (!err) {
var annonceChange = false;
chguser.salt = salt;
chguser.hash = hash;
chguser.passhint = command.hint;
chguser.passchange = Math.floor(Date.now() / 1000);
if (command.resetNextLogin == true) { chguser.passchange = -1; } else { chguser.passchange = Math.floor(Date.now() / 1000); }
delete chguser.passtype; // Remove the password type if one was present.
if (command.removeMultiFactor == true) {
if (chguser.otpsecret) { delete chguser.otpsecret; annonceChange = true; }
if (chguser.otphkeys) { delete chguser.otphkeys; annonceChange = true; }
if (chguser.otpkeys) { delete chguser.otpkeys; annonceChange = true; }
if (chguser.otpsecret) { delete chguser.otpsecret; }
if (chguser.otphkeys) { delete chguser.otphkeys; }
if (chguser.otpkeys) { delete chguser.otpkeys; }
}
obj.db.SetUser(chguser);
if (annonceChange == true) { obj.parent.parent.DispatchEvent(['*', 'server-users', user._id, chguser._id], obj, { etype: 'user', username: user.name, account: obj.parent.CloneSafeUser(chguser), action: 'accountchange', msg: 'Removed 2nd factor auth.', domain: domain.id }); }
obj.parent.parent.DispatchEvent(['*', 'server-users', user._id, chguser._id], obj, { etype: 'user', username: user.name, account: obj.parent.CloneSafeUser(chguser), action: 'accountchange', msg: 'Changed account credentials.', domain: domain.id });
} else {
// Report that the password change failed
// TODO

View File

@ -48,7 +48,7 @@
"_NewAccountEmailDomains": [ "sample.com" ],
"Footer": "<a href='https://twitter.com/mytwitter'>Twitter</a>",
"_CertUrl": "https://192.168.2.106:443/",
"_PasswordRequirements": { "min": 8, "max": 128, "upper": 1, "lower": 1, "numeric": 1, "nonalpha": 1 },
"_PasswordRequirements": { "min": 8, "max": 128, "upper": 1, "lower": 1, "numeric": 1, "nonalpha": 1, "reset": 90 },
"_AgentNoProxy": true,
"_GeoLocation": true,
"_UserAllowedIP": "127.0.0.1,192.168.1.0/24",

View File

@ -23,10 +23,10 @@ module.exports.CreateSwarmServer = function (parent, db, args, certificates) {
obj.legacyAgentConnections = {};
obj.migrationAgents = {};
obj.agentActionCount = {};
const common = require('./common.js');
//const net = require('net');
obj.stats = { blockedConnect: 0, connectCount: 0, clientCertConnectCount: 0, noCertConnectCount: 0, bytesIn: 0, bytesOut: 0, httpGetRequest: 0, pushedAgents: {}, close: 0, onclose: 0 }
const tls = require('tls');
const forge = require('node-forge');
const common = require('./common.js');
const LegacyMeshProtocol = {
NODEPUSH: 1, // Used to send a node block to another peer.
@ -149,26 +149,41 @@ module.exports.CreateSwarmServer = function (parent, db, args, certificates) {
// Called when a legacy agent connects to this server
function onConnection(socket) {
// Check for blocked IP address
if (checkSwarmIpAddress(socket, obj.args.swarmallowedip) == false) { Debug(1, "SWARM:New blocked agent connection"); return; }
if (checkSwarmIpAddress(socket, obj.args.swarmallowedip) == false) { obj.stats.blockedConnect++; Debug(1, "SWARM:New blocked agent connection"); return; }
obj.stats.connectCount++;
socket.tag = { first: true, clientCert: socket.getPeerCertificate(true), accumulator: "", socket: socket };
socket.setEncoding('binary');
socket.pingTimer = setInterval(function () { obj.SendCommand(socket, LegacyMeshProtocol.PING); }, 20000);
Debug(1, 'SWARM:New legacy agent connection');
if ((socket.tag.clientCert == null) || (socket.tag.clientCert.subject == null)) { obj.stats.noCertConnectCount++; } else { obj.stats.clientCertConnectCount++; }
socket.addListener("data", function (data) {
if (args.swarmdebug) { var buf = Buffer.from(data, "binary"); console.log('SWARM <-- (' + buf.length + '):' + buf.toString('hex')); } // Print out received bytes
obj.stats.bytesIn += data.length;
socket.tag.accumulator += data;
// Detect if this is an HTTPS request, if it is, return a simple answer and disconnect. This is useful for debugging access to the MPS port.
if (socket.tag.first == true) {
if (socket.tag.accumulator.length < 3) return;
if (socket.tag.accumulator.substring(0, 3) == 'GET') { /*console.log("Swarm Connection, HTTP GET detected: " + socket.remoteAddress);*/ socket.write('HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body>MeshCentral2 legacy swarm server.<br />MeshCentral1 mesh agents should connect here for updates.</body></html>'); socket.end(); return; }
if (socket.tag.accumulator.substring(0, 3) == 'GET') {
obj.stats.httpGetRequest++;
/*console.log("Swarm Connection, HTTP GET detected: " + socket.remoteAddress);*/
socket.write('HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body>MeshCentral2 legacy swarm server.<br />MeshCentral1 mesh agents should connect here for updates.</body></html>');
socket.end();
return;
}
socket.tag.first = false;
}
// A client certificate is required
if (!socket.tag.clientCert.subject) { /*console.log("Swarm Connection, no client cert: " + socket.remoteAddress);*/ socket.write('HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nMeshCentral2 legacy swarm server.\r\nNo client certificate given.'); socket.end(); return; }
if ((socket.tag.clientCert == null) || (socket.tag.clientCert.subject == null)) {
/*console.log("Swarm Connection, no client cert: " + socket.remoteAddress);*/
socket.write('HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nMeshCentral2 legacy swarm server.\r\nNo client certificate given.');
socket.end();
return;
}
try {
// Parse all of the agent binary command data we can
@ -215,6 +230,10 @@ module.exports.CreateSwarmServer = function (parent, db, args, certificates) {
socket.tag.updatePtr = 0;
//console.log('Performing legacy agent update from ' + nodeblock.agentversion + '.' + nodeblock.agenttype + ' to ' + socket.tag.update.ver + '.' + socket.tag.update.arch + ' on ' + nodeblock.agentname + '.');
// Update stats
if (obj.stats.pushedAgents[nodeblock.agenttype] == null) { obj.stats.pushedAgents[nodeblock.agenttype] = {}; }
if (obj.stats.pushedAgents[nodeblock.agenttype][nextAgentVersion] == null) { obj.stats.pushedAgents[nodeblock.agenttype][nextAgentVersion] = 0; } else { obj.stats.pushedAgents[nodeblock.agenttype][nextAgentVersion]++; }
// Start the agent download using the task limiter so not to flood the server. Low priority task
obj.parent.taskLimiter.launch(function (socket, taskid, taskLimiterQueue) {
if (socket.xclosed == 1) {
@ -286,6 +305,7 @@ module.exports.CreateSwarmServer = function (parent, db, args, certificates) {
}
socket.addListener("close", function () {
obj.stats.onclose++;
Debug(1, 'Swarm:Connection closed');
if (socket.pingTimer != null) { clearInterval(socket.pingTimer); delete socket.pingTimer; }
if (socket.tag && (typeof socket.tag.taskid == 'number')) {
@ -358,6 +378,7 @@ module.exports.CreateSwarmServer = function (parent, db, args, certificates) {
// Disconnect legacy agent connection
obj.close = function (socket) {
obj.stats.close++;
try { socket.close(); } catch (e) { }
socket.xclosed = 1;
};
@ -368,6 +389,7 @@ module.exports.CreateSwarmServer = function (parent, db, args, certificates) {
};
function Write(socket, data) {
obj.stats.bytesOut += data.length;
if (args.swarmdebug) {
// Print out sent bytes
var buf = Buffer.from(data, "binary");

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -243,7 +243,7 @@
<div style="margin-left:9px;margin-bottom:8px">
<div style="margin-top:5px"><span id="verifyEmailId" style="display:none"><a onclick="account_showVerifyEmail()" style="cursor:pointer">Verify email</a></span></div>
<div style="margin-top:5px"><a onclick="account_showChangeEmail()" style="cursor:pointer">Change email address</a></div>
<div style="margin-top:5px"><a onclick="account_showChangePassword()" style="cursor:pointer">Change password</a></div>
<div style="margin-top:5px"><a onclick="account_showChangePassword()" style="cursor:pointer">Change password</a><span id="p2nextPasswordUpdateTime"></span></div>
<div style="margin-top:5px"><a onclick="account_showDeleteAccount()" style="cursor:pointer">Delete account</a></div>
</div>
<br style=clear:both />
@ -654,8 +654,21 @@
QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true));
QV('manageAuthApp', features & 4096);
QV('manageOtp', ((features & 4096) != 0) && ((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0)));
if (typeof userinfo.passchange == 'number') {
if (userinfo.passchange == -1) { QH('p2nextPasswordUpdateTime', ' - Reset on next login.'); }
else if ((passRequirements != null) && (typeof passRequirements.reset == 'number')) {
var seconds = (userinfo.passchange) + (passRequirements.reset * 86400) - Math.floor(Date.now() / 1000);
if (seconds < 0) { QH('p2nextPasswordUpdateTime', ' - Reset on next login.'); }
else if (seconds < 3600) { QH('p2nextPasswordUpdateTime', ' - Reset in ' + Math.floor(seconds / 60) + ' minute' + addLetterS(Math.floor(seconds / 60)) + '.'); }
else if (seconds < 86400) { QH('p2nextPasswordUpdateTime', ' - Reset in ' + Math.floor(seconds / 3600) + ' hour' + addLetterS(Math.floor(seconds / 3600)) + '.'); }
else { QH('p2nextPasswordUpdateTime', ' - Reset in ' + Math.floor(seconds / 86400) + ' day' + addLetterS(Math.floor(seconds / 86400)) + '.'); }
}
}
}
function addLetterS(x) { return (x > 1) ? 's' : ''; }
function onMessage(server, message) {
switch (message.action) {
case 'serverinfo': {

View File

@ -260,7 +260,7 @@
<p style="margin-left:40px">
<span id="verifyEmailId" style="display:none"><a onclick="account_showVerifyEmail()" style="cursor:pointer">Verify email</a><br /></span>
<a onclick="account_showChangeEmail()" style="cursor:pointer">Change email address</a><br />
<a onclick="account_showChangePassword()" style="cursor:pointer">Change password</a><br />
<a onclick="account_showChangePassword()" style="cursor:pointer">Change password</a><span id="p2nextPasswordUpdateTime"></span><br />
<a onclick="account_showDeleteAccount()" style="cursor:pointer">Delete account</a><br />
</p>
<br style=clear:both />
@ -1190,8 +1190,21 @@
QV('authAppSetupCheck', userinfo.otpsecret == 1);
QV('authKeySetupCheck', userinfo.otphkeys > 0);
QV('authCodesSetupCheck', userinfo.otpkeys > 0);
if (typeof userinfo.passchange == 'number') {
if (userinfo.passchange == -1) { QH('p2nextPasswordUpdateTime', ' - Reset on next login.'); }
else if ((passRequirements != null) && (typeof passRequirements.reset == 'number')) {
var seconds = (userinfo.passchange) + (passRequirements.reset * 86400) - Math.floor(Date.now() / 1000);
if (seconds < 0) { QH('p2nextPasswordUpdateTime', ' - Reset on next login.'); }
else if (seconds < 3600) { QH('p2nextPasswordUpdateTime', ' - Reset in ' + Math.floor(seconds / 60) + ' minute' + addLetterS(Math.floor(seconds / 60)) + '.'); }
else if (seconds < 86400) { QH('p2nextPasswordUpdateTime', ' - Reset in ' + Math.floor(seconds / 3600) + ' hour' + addLetterS(Math.floor(seconds / 3600)) + '.'); }
else { QH('p2nextPasswordUpdateTime', ' - Reset in ' + Math.floor(seconds / 86400) + ' day' + addLetterS(Math.floor(seconds / 86400)) + '.'); }
}
}
}
function addLetterS(x) { return (x > 1) ? 's' : ''; }
function onMessage(server, message) {
switch (message.action) {
case 'serverstats': {
@ -1767,7 +1780,9 @@
}
case 'login': {
// Update the last login time
if (users != null && users['user/' + domain + '/' + message.event.username.toLowerCase()]) { users['user/' + domain + '/' + message.event.username.toLowerCase()].login = Math.floor(message.event.time / 1000); }
if (users != null && users['user/' + domain + '/' + message.event.username.toLowerCase()]) {
users['user/' + domain + '/' + message.event.username.toLowerCase()].login = Math.floor(new Date(message.event.time).getTime() / 1000);
}
break;
}
case 'scanamtdevice': {
@ -6584,6 +6599,8 @@
if (user.quota) x += addDeviceAttribute('Server Quota', EscapeHtml(parseInt(user.quota) / 1024) + ' k');
x += addDeviceAttribute('Creation', new Date(user.creation * 1000).toLocaleString());
if (user.login) x += addDeviceAttribute('Last Login', new Date(user.login * 1000).toLocaleString());
if (user.passchange == -1) { x += addDeviceAttribute('Password', 'Will be changed on next login.'); }
else if (user.passchange) { x += addDeviceAttribute('Password', 'Last changed: ' + new Date(user.passchange * 1000).toLocaleString()); }
var linkCount = 0, linkCountStr = '<i>None<i>';
if (user.links) {
@ -6674,6 +6691,7 @@
x += addHtmlValue('Password', '<input id=p4pass2 type=password style=width:230px maxlength=256 onchange=showCreateNewAccountDialogValidate(1) onkeyup=showCreateNewAccountDialogValidate(1)></input>');
x += addHtmlValue('Password hint', '<input id=p4hint type=text style=width:230px maxlength=256></input>');
if (passRequirements) { var r = []; for (var i in passRequirements) { r.push(i + ':' + passRequirements[i]); } x += '<div style=font-size:x-small;padding:6px>Requirements: ' + r.join(', ') + '.</div>'; }
x += '<div><input id=p4resetNextLogin type=checkbox />Force password reset on next login.</div>';
if (multiFactor == 1) { x += '<div><input id=p4twoFactorRemove type=checkbox />Remove all 2nd factor authentication.</div>'; }
setDialogMode(2, "Change Password for " + EscapeHtml(currentUser.name), 3, p30showUserChangePassDialogEx, x, multiFactor);
showCreateNewAccountDialogValidate(1);
@ -6683,7 +6701,7 @@
function p30showUserChangePassDialogEx(b, tag) {
var removeMultiFactor = false;
if ((tag == 1) && (Q('p4twoFactorRemove').checked == true)) { removeMultiFactor = true; }
if (Q('p4pass1').value == Q('p4pass2').value) { meshserver.send({ action: 'changeuserpass', user: currentUser.name, pass: Q('p4pass1').value, hint: Q('p4hint').value, removeMultiFactor: removeMultiFactor }); }
if (Q('p4pass1').value == Q('p4pass2').value) { meshserver.send({ action: 'changeuserpass', user: currentUser.name, pass: Q('p4pass1').value, hint: Q('p4hint').value, removeMultiFactor: removeMultiFactor, resetNextLogin: Q('p4resetNextLogin').checked }); }
}
function p30showDeleteUserDialog() {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -188,6 +188,35 @@
</form>
</div>
<div id=resetpasswordpanel style="position:relative;background-color: #979797;border-radius:16px;width:300px;padding:16px;text-align:center;display:none">
<form action=resetpassword method=post>
<div id=message6>
{{{message}}}
</div>
<div id="rpasswordPolicyCallout" style="left:-10px;width:100px;display:none;position:absolute;background-color:#FFC;border-radius:5px;padding:5px;box-shadow:0px 0px 15px #666;font-size:10px"></div>
<table>
<tr>
<td id="rnuPass1" width=100 align=right>Password:</td>
<td><input id=rapassword1 type=password name=rpassword1 autocomplete=off maxlength=256 onkeydown=haltReturn(event) onchange=validatePassReset(3,event) onkeyup=validatePassReset(3,event) /></td>
</tr>
<tr>
<td id="rnuPass2" align=right>Password:</td>
<td><input id=rapassword2 type=password name=rpassword2 autocomplete=off maxlength=256 onkeydown=haltReturn(event) onchange=validatePassReset(4,event) onkeyup=validatePassReset(4,event) /></td>
</tr>
<tr>
<td id="rnuHint" align=right>Password Hint:</td>
<td><input id=rapasswordhint type=text name=rpasswordhint autocomplete=off maxlength=256 onkeydown=haltReturn(event) onchange=validatePassReset(5,event) onkeyup=validatePassReset(5,event) /></td>
</tr>
<tr>
<td colspan=2>
<div style=float:right><input id=resetPassButton type=submit value="Reset Password" disabled="disabled" /></div>
<div id=rpassWarning style="padding-top:6px"></div>
</td>
</tr>
</table>
<hr /><a onclick=xgo(1) style=cursor:pointer>Back to login</a>
</form>
</div>
</td>
</tr>
@ -288,6 +317,7 @@
QV('message3', false);
QV('message4', false);
QV('message5', false);
QV('message6', false);
go(x);
}
@ -299,10 +329,13 @@
QV('resetpanel', x == 3);
QV('tokenpanel', x == 4);
QV('resettokenpanel', x == 5);
QV('resetpasswordpanel', x == 6);
if (x == 1) { Q('username').focus(); }
if (x == 2) { Q('ausername').focus(); }
if (x == 3) { Q('remail').focus(); }
if (x == 4) { Q('tokenInput').focus(); }
if (x == 5) { Q('resetTokenInput').focus(); }
if (x == 6) { Q('rapassword1').focus(); }
}
function validateLogin(box, e) {
@ -355,6 +388,52 @@
if (e != null) { haltEvent(e); }
}
function validatePassReset(box, e) {
setDialogMode(0);
var pass1ok = (Q('rapassword1').value.length > 0);
var pass2ok = (Q('rapassword2').value.length > 0) && (Q('rapassword2').value == Q('rapassword1').value);
var ok = (pass1ok && pass2ok);
// Color the fields
QS('rnuPass1').color = pass1ok ? 'black' : '#7b241c';
QS('rnuPass2').color = pass2ok ? 'black' : '#7b241c';
if (Q('rapassword1').value == '') {
QH('rpassWarning', '');
QV('rpasswordPolicyCallout', false);
} else {
if (passRequirements == null || passRequirements == '') {
// No password requirements, display password strength
var passStrength = checkPasswordStrength(Q('rapassword1').value);
if (passStrength >= 80) { QH('rpassWarning', '<span style=color:green><b>Strong Password</b><span>'); }
else if (passStrength >= 60) { QH('rpassWarning', '<span style=color:blue><b>Good Password</b><span>'); }
else { QH('rpassWarning', '<span style=color:red><b>Weak Password</b><span>'); }
} else {
// Password requirements provided, use that
var passReq = checkPasswordRequirements(Q('rapassword1').value, passRequirements);
if (passReq == false) {
ok = false;
QS('rnuPass1').color = '#7b241c';
QS('rnuPass2').color = '#7b241c';
QH('rpassWarning', '<div style=color:red;cursor:pointer onclick=showPasswordPolicy()><b>Password Policy</b><div>'); // This is also a link to the password policy
QV('rpasswordPolicyCallout', true);
QH('rpasswordPolicyCallout', passwordPolicyText(Q('rapassword1').value));
} else {
QH('rpassWarning', '');
QV('rpasswordPolicyCallout', false);
}
}
}
if ((e != null) && (e.keyCode == 13)) {
if (box == 2) { Q('rapassword1').focus(); }
if (box == 3) { Q('rapassword2').focus(); }
if (box == 4) { Q('rapasswordhint').focus(); }
if (box == 6) { Q('resetPassButton').click(); }
}
if (e != null) { haltEvent(e); }
QE('resetPassButton', ok);
}
function validateReset(e) {
setDialogMode(0);
var x = validateEmail(Q('remail').value);

View File

@ -167,19 +167,19 @@
</tr>
<tr>
<td id="nuPass1" align=right>Password:</td>
<td><input id=apassword1 type=password name=password1 autocomplete=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(3) onkeyup=validateCreate(3,event) /></td>
<td><input id=apassword1 type=password name=password1 autocomplete=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(3,event) onkeyup=validateCreate(3,event) /></td>
</tr>
<tr>
<td id="nuPass2" align=right>Password:</td>
<td><input id=apassword2 type=password name=password2 autocomplete=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(4) onkeyup=validateCreate(4,event) /></td>
<td><input id=apassword2 type=password name=password2 autocomplete=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(4,event) onkeyup=validateCreate(4,event) /></td>
</tr>
<tr>
<td id="nuHint" align=right>Password Hint:</td>
<td><input id=apasswordhint type=text name=apasswordhint autocomplete=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(5) onkeyup=validateCreate(5,event) /></td>
<td><input id=apasswordhint type=text name=apasswordhint autocomplete=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(5,event) onkeyup=validateCreate(5,event) /></td>
</tr>
<tr id=newAccountPass title="Enter the account creation token">
<td id="nuToken" align=right>Creation Token:</td>
<td><input id=anewaccountpass type=password name=anewaccountpass autocomplete=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(6) onkeyup=validateCreate(6,event) /></td>
<td><input id=anewaccountpass type=password name=anewaccountpass autocomplete=off maxlength=256 onkeydown=haltReturn(event) onchange=validateCreate(6,event) onkeyup=validateCreate(6,event) /></td>
</tr>
<tr>
<td colspan=2>
@ -258,6 +258,35 @@
<hr /><a onclick=xgo(1) style=cursor:pointer>Back to login</a>
</form>
</div>
<div id=resetpasswordpanel style="position:relative;background-color: #979797;border-radius:16px;width:300px;padding:16px;text-align:center;display:none">
<form action=resetpassword method=post>
<div id=message6>
{{{message}}}
</div>
<div id="rpasswordPolicyCallout" style="left:-10px;width:100px;display:none;position:absolute;background-color:#FFC;border-radius:5px;padding:5px;box-shadow:0px 0px 15px #666;font-size:10px"></div>
<table>
<tr>
<td id="rnuPass1" width=100 align=right>Password:</td>
<td><input id=rapassword1 type=password name=rpassword1 autocomplete=off maxlength=256 onkeydown=haltReturn(event) onchange=validatePassReset(3,event) onkeyup=validatePassReset(3,event) /></td>
</tr>
<tr>
<td id="rnuPass2" align=right>Password:</td>
<td><input id=rapassword2 type=password name=rpassword2 autocomplete=off maxlength=256 onkeydown=haltReturn(event) onchange=validatePassReset(4,event) onkeyup=validatePassReset(4,event) /></td>
</tr>
<tr>
<td id="rnuHint" align=right>Password Hint:</td>
<td><input id=rapasswordhint type=text name=rpasswordhint autocomplete=off maxlength=256 onkeydown=haltReturn(event) onchange=validatePassReset(5,event) onkeyup=validatePassReset(5,event) /></td>
</tr>
<tr>
<td colspan=2>
<div style=float:right><input id=resetPassButton type=submit value="Reset Password" disabled="disabled" /></div>
<div id=rpassWarning style="padding-top:6px"></div>
</td>
</tr>
</table>
<hr /><a onclick=xgo(1) style=cursor:pointer>Back to login</a>
</form>
</div>
</td>
</tr>
</table>
@ -373,6 +402,7 @@
QV('message3', false);
QV('message4', false);
QV('message5', false);
QV('message6', false);
go(x);
}
@ -384,10 +414,13 @@
QV('resetpanel', x == 3);
QV('tokenpanel', x == 4);
QV('resettokenpanel', x == 5);
QV('resetpasswordpanel', x == 6);
if (x == 1) { Q('username').focus(); }
if (x == 2) { Q('ausername').focus(); }
if (x == 3) { Q('remail').focus(); }
if (x == 4) { Q('tokenInput').focus(); }
if (x == 5) { Q('resetTokenInput').focus(); }
if (x == 6) { Q('rapassword1').focus(); }
}
function validateLogin(box, e) {
@ -452,6 +485,52 @@
QE('createButton', ok);
}
function validatePassReset(box, e) {
setDialogMode(0);
var pass1ok = (Q('rapassword1').value.length > 0);
var pass2ok = (Q('rapassword2').value.length > 0) && (Q('rapassword2').value == Q('rapassword1').value);
var ok = (pass1ok && pass2ok);
// Color the fields
QS('rnuPass1').color = pass1ok ? 'black' : '#7b241c';
QS('rnuPass2').color = pass2ok ? 'black' : '#7b241c';
if (Q('rapassword1').value == '') {
QH('rpassWarning', '');
QV('rpasswordPolicyCallout', false);
} else {
if (passRequirements == null || passRequirements == '') {
// No password requirements, display password strength
var passStrength = checkPasswordStrength(Q('rapassword1').value);
if (passStrength >= 80) { QH('rpassWarning', '<span style=color:green><b>Strong Password</b><span>'); }
else if (passStrength >= 60) { QH('rpassWarning', '<span style=color:blue><b>Good Password</b><span>'); }
else { QH('rpassWarning', '<span style=color:red><b>Weak Password</b><span>'); }
} else {
// Password requirements provided, use that
var passReq = checkPasswordRequirements(Q('rapassword1').value, passRequirements);
if (passReq == false) {
ok = false;
QS('rnuPass1').color = '#7b241c';
QS('rnuPass2').color = '#7b241c';
QH('rpassWarning', '<div style=color:red;cursor:pointer onclick=showPasswordPolicy()><b>Password Policy</b><div>'); // This is also a link to the password policy
QV('rpasswordPolicyCallout', true);
QH('rpasswordPolicyCallout', passwordPolicyText(Q('rapassword1').value));
} else {
QH('rpassWarning', '');
QV('rpasswordPolicyCallout', false);
}
}
}
if ((e != null) && (e.keyCode == 13)) {
if (box == 2) { Q('rapassword1').focus(); }
if (box == 3) { Q('rapassword2').focus(); }
if (box == 4) { Q('rapasswordhint').focus(); }
if (box == 6) { Q('resetPassButton').click(); }
}
if (e != null) { haltEvent(e); }
QE('resetPassButton', ok);
}
function passwordPolicyText(pass) {
var policy = '<div style=text-align:left>';
var counts = strCount(pass);

View File

@ -419,7 +419,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
var user = obj.users[userid];
// Check if this user has 2-step login active
if (checkUserOneTimePasswordRequired(domain, user)) {
if ((req.session.loginmode != '6') && checkUserOneTimePasswordRequired(domain, user)) {
checkUserOneTimePassword(req, domain, user, req.body.token, req.body.hwtoken, function (result) {
if (result == false) {
var randomWaitTime = 0;
@ -439,14 +439,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
}, randomWaitTime);
} else {
// Login succesful
completeLoginRequest(req, res, domain, user, userid);
completeLoginRequest(req, res, domain, user, userid, xusername, xpassword);
}
});
return;
}
// Login succesful
completeLoginRequest(req, res, domain, user, userid);
completeLoginRequest(req, res, domain, user, userid, xusername, xpassword);
} else {
// Login failed, wait a random delay
setTimeout(function () {
@ -466,10 +466,22 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
});
}
function completeLoginRequest(req, res, domain, user, userid) {
function completeLoginRequest(req, res, domain, user, userid, xusername, xpassword) {
// Check if we need to change the password
if ((typeof user.passchange == 'number') && ((user.passchange == -1) || ((typeof domain.passwordrequirements == 'object') && (typeof domain.passwordrequirements.reset == 'number') && (user.passchange + (domain.passwordrequirements.reset * 86400) < Math.floor(Date.now() / 1000))))) {
// Request a password change
req.session.loginmode = '6';
req.session.error = '<b style=color:#8C001A>Password change requested.</b>';
req.session.resettokenusername = xusername;
req.session.resettokenpassword = xpassword;
res.redirect(domain.url);
return;
}
// Save login time
user.login = Math.floor(Date.now() / 1000);
obj.db.SetUser(user);
obj.parent.DispatchEvent(['*'], obj, { etype: 'user', username: user.name, account: obj.CloneSafeUser(user), action: 'login', msg: 'Account login', domain: domain.id });
// Regenerate session when signing in to prevent fixation
//req.session.regenerate(function () {
@ -506,8 +518,6 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
res.redirect(domain.url);
}
//});
obj.parent.DispatchEvent(['*'], obj, { etype: 'user', username: user.name, action: 'login', msg: 'Account login', domain: domain.id });
}
function handleCreateAccountRequest(req, res) {
@ -521,7 +531,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
var i = -1;
if (typeof req.body.email == 'string') { i = req.body.email.indexOf('@'); }
if (i == -1) {
req.session.loginmode = 2;
req.session.loginmode = '2';
req.session.error = '<b style=color:#8C001A>Unable to create account.</b>';
res.redirect(domain.url);
return;
@ -529,7 +539,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
var emailok = false, emaildomain = req.body.email.substring(i + 1).toLowerCase();
for (var i in domain.newaccountemaildomains) { if (emaildomain == domain.newaccountemaildomains[i].toLowerCase()) { emailok = true; } }
if (emailok == false) {
req.session.loginmode = 2;
req.session.loginmode = '2';
req.session.error = '<b style=color:#8C001A>Unable to create account.</b>';
res.redirect(domain.url);
return;
@ -539,33 +549,33 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Check if we exceed the maximum number of user accounts
obj.db.isMaxType(domain.limits.maxuseraccounts, 'user', domain.id, function (maxExceed) {
if (maxExceed) {
req.session.loginmode = 2;
req.session.loginmode = '2';
req.session.error = '<b style=color:#8C001A>Account limit reached.</b>';
console.log('max', req.session);
res.redirect(domain.url);
} else {
if (!obj.common.validateUsername(req.body.username, 1, 64) || !obj.common.validateEmail(req.body.email, 1, 256) || !obj.common.validateString(req.body.password1, 1, 256) || !obj.common.validateString(req.body.password2, 1, 256) || (req.body.password1 != req.body.password2) || req.body.username == '~' || !obj.common.checkPasswordRequirements(req.body.password1, domain.passwordrequirements)) {
req.session.loginmode = 2;
req.session.loginmode = '2';
req.session.error = '<b style=color:#8C001A>Unable to create account.</b>';
res.redirect(domain.url);
} else {
// Check if this email was already verified
obj.db.GetUserWithVerifiedEmail(domain.id, req.body.email, function (err, docs) {
if (docs.length > 0) {
req.session.loginmode = 2;
req.session.loginmode = '2';
req.session.error = '<b style=color:#8C001A>Existing account with this email address.</b>';
res.redirect(domain.url);
} else {
// Check if there is domain.newAccountToken, check if supplied token is valid
if ((domain.newaccountspass != null) && (domain.newaccountspass != '') && (req.body.anewaccountpass != domain.newaccountspass)) {
req.session.loginmode = 2;
req.session.loginmode = '2';
req.session.error = '<b style=color:#8C001A>Invalid account creation token.</b>';
res.redirect(domain.url);
return;
}
// Check if user exists
if (obj.users['user/' + domain.id + '/' + req.body.username.toLowerCase()]) {
req.session.loginmode = 2;
req.session.loginmode = '2';
req.session.error = '<b style=color:#8C001A>Username already exists.</b>';
} else {
var hint = req.body.apasswordhint;
@ -599,6 +609,82 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
});
}
// Called to process an account password reset
function handleResetPasswordRequest(req, res) {
const domain = checkUserIpAddress(req, res);
// Check everything is ok
if ((domain == null) || (domain.auth == 'sspi') || (typeof req.body.rpassword1 != 'string') || (typeof req.body.rpassword2 != 'string') || (req.body.rpassword1 != req.body.rpassword2) || (typeof req.body.rpasswordhint != 'string') || (req.session == null) || (typeof req.session.resettokenusername != 'string') || (typeof req.session.resettokenpassword != 'string')) {
delete req.session.loginmode;
delete req.session.tokenusername;
delete req.session.tokenpassword;
delete req.session.resettokenusername;
delete req.session.resettokenpassword;
delete req.session.tokenemail;
delete req.session.success;
delete req.session.error;
delete req.session.passhint;
res.redirect(domain.url);
return;
}
// Authenticate the user
obj.authenticate(req.session.resettokenusername, req.session.resettokenpassword, domain, function (err, userid, passhint) {
if (userid) {
// Login
var user = obj.users[userid];
// If we have password requirements, check this here.
if (!obj.common.checkPasswordRequirements(req.body.rpassword1, domain.passwordrequirements)) {
req.session.loginmode = '6';
req.session.error = '<b style=color:#8C001A>Password rejected, use a different one.</b>';
res.redirect(domain.url);
return;
}
// Check if the password is the same as the previous one
require('./pass').hash(req.body.rpassword1, user.salt, function (err, hash) {
if (user.hash == hash) {
// This is the same password, request a password change again
req.session.loginmode = '6';
req.session.error = '<b style=color:#8C001A>Password rejected, use a different one.</b>';
res.redirect(domain.url);
} else {
// Update the password, use a different salt.
require('./pass').hash(req.body.rpassword1, function (err, salt, hash) {
if (err) throw err;
user.salt = salt;
user.hash = hash;
user.passhint = req.body.rpasswordhint;
user.passchange = Math.floor(Date.now() / 1000);
delete user.passtype;
obj.db.SetUser(user);
obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, { etype: 'user', username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msg: 'User password reset', domain: domain.id });
// Login succesful
req.session.userid = userid;
req.session.domainid = domain.id;
completeLoginRequest(req, res, domain, obj.users[userid], userid, req.session.tokenusername, req.session.tokenpassword);
});
}
});
} else {
// Failed, error out.
delete req.session.loginmode;
delete req.session.tokenusername;
delete req.session.tokenpassword;
delete req.session.resettokenusername;
delete req.session.resettokenpassword;
delete req.session.tokenemail;
delete req.session.success;
delete req.session.error;
delete req.session.passhint;
res.redirect(domain.url);
return;
}
});
}
// Called to process an account reset request
function handleResetAccountRequest(req, res) {
const domain = checkUserIpAddress(req, res);
@ -610,13 +696,13 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Check the email stirng format
if (!email || checkEmail(email) == false) {
req.session.loginmode = 3;
req.session.loginmode = '3';
req.session.error = '<b style=color:#8C001A>Invalid email.</b>';
res.redirect(domain.url);
} else {
obj.db.GetUserWithVerifiedEmail(domain.id, email, function (err, docs) {
if ((err != null) || (docs.length == 0)) {
req.session.loginmode = 3;
req.session.loginmode = '3';
req.session.error = '<b style=color:#8C001A>Account not found.</b>';
res.redirect(domain.url);
} else {
@ -635,11 +721,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
delete req.session.tokenemail;
if (obj.parent.mailserver != null) {
obj.parent.mailserver.sendAccountResetMail(domain, user.name, user.email);
req.session.loginmode = 1;
req.session.loginmode = '1';
req.session.error = '<b style=color:darkgreen>Hold on, reset mail sent.</b>';
res.redirect(domain.url);
} else {
req.session.loginmode = 3;
req.session.loginmode = '3';
req.session.error = '<b style=color:#8C001A>Unable to sent email.</b>';
res.redirect(domain.url);
}
@ -649,11 +735,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// No second factor, send email to perform recovery.
if (obj.parent.mailserver != null) {
obj.parent.mailserver.sendAccountResetMail(domain, user.name, user.email);
req.session.loginmode = 1;
req.session.loginmode = '1';
req.session.error = '<b style=color:darkgreen>Hold on, reset mail sent.</b>';
res.redirect(domain.url);
} else {
req.session.loginmode = 3;
req.session.loginmode = '3';
req.session.error = '<b style=color:#8C001A>Unable to sent email.</b>';
res.redirect(domain.url);
}
@ -1074,7 +1160,6 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (err != null) message = '<p class="msg error">' + err + '</p>';
if (msg != null) message = '<p class="msg success">' + msg + '</p>';
if (passhint != null) passhint = EscapeHtml(passhint);
var emailcheck = ((obj.parent.mailserver != null) && (domain.auth != 'sspi'));
if (obj.args.minify && !req.query.nominify) {
@ -2271,6 +2356,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
obj.app.post(url + 'changepassword', handlePasswordChangeRequest);
obj.app.post(url + 'deleteaccount', handleDeleteAccountRequest);
obj.app.post(url + 'createaccount', handleCreateAccountRequest);
obj.app.post(url + 'resetpassword', handleResetPasswordRequest);
obj.app.post(url + 'resetaccount', handleResetAccountRequest);
obj.app.get(url + 'checkmail', handleCheckMailRequest);
obj.app.post(url + 'amtevents.ashx', obj.handleAmtEventRequest);