Added user account, user session and agent session per-domain limits.

Ylian Saint-Hilaire committed Feb 11, 2019 at 14:41 UTC 91282677cdb77bbef0f9136d0df9f0053a8fb4a9
11 files changed +212 -29
certoperations.js
+4 -2
@@ -112,7 +112,9 @@ module.exports.CertificateOperations = function (parent) {
112
113 // Create a self-signed certificate
114 obj.GenerateRootCertificate = function (addThumbPrintToName, commonName, country, organization, strong) {
115 - var keys = obj.pki.rsa.generateKeyPair((strong == true) ? 3072 : 2048);
115 + // TODO: Use Async key generation to use web workers and go a lot faster.
116 + // rsa.generateKeyPair({ bits: 3072, e: 0x10001, workers: -1 }, function (err, keypair) { /*keypair.privateKey, keypair.publicKey*/ });
117 + var keys = obj.pki.rsa.generateKeyPair({ bits: (strong == true) ? 3072 : 2048, e: 0x10001 });
118 var cert = obj.pki.createCertificate();
119 cert.publicKey = keys.publicKey;
120 cert.serialNumber = String(Math.floor((Math.random() * 100000) + 1));
@@ -136,7 +138,7 @@ module.exports.CertificateOperations = function (parent) {
138
139 // Issue a certificate from a root
140 obj.IssueWebServerCertificate = function (rootcert, addThumbPrintToName, commonName, country, organization, extKeyUsage, strong) {
139 - var keys = obj.pki.rsa.generateKeyPair((strong == true) ? 3072 : 2048);
141 + var keys = obj.pki.rsa.generateKeyPair({ bits: (strong == true) ? 3072 : 2048, e: 0x10001 });
142 var cert = obj.pki.createCertificate();
143 cert.publicKey = keys.publicKey;
144 cert.serialNumber = String(Math.floor((Math.random() * 100000) + 1));
db.js
+1 -1
@@ -175,7 +175,7 @@ module.exports.CreateDB = function (parent) {
175 obj.getPowerTimeline = function (nodeid, func) { if (obj.databaseType == 1) { obj.file.find({ type: 'power', node: { $in: ['*', nodeid] } }).sort({ time: 1 }).exec(func); } else { obj.file.find({ type: 'power', node: { $in: ['*', nodeid] } }).sort({ time: 1 }, func); } };
176 obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }, func); };
177 obj.getAmtUuidNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }, func); };
178 - obj.isMaxType = function (max, type, func) { if (max == null) { func(false); } else { obj.file.count({ type: type }, function (err, count) { func((err != null) || (count > max)); }); } }
178 + obj.isMaxType = function (max, type, domainid, func) { if (max == null) { func(false); } else { obj.file.count({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max)); }); } }
179
180 // Read a configuration file from the database
181 obj.getConfigFile = function (path, func) { obj.Get('cfile/' + path, func); }
meshagent.js
+10
@@ -368,6 +368,16 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
368 if ((obj.authenticated != 1) || (obj.meshid == null) || obj.pendingCompleteAgentConnection) return;
369 obj.pendingCompleteAgentConnection = true;
370
371 + // Check if we have too many agent sessions
372 + if (typeof domain.limits.maxagentsessions == 'number') {
373 + // Count the number of agent sessions for this domain
374 + var domainAgentSessionCount = 0;
375 + for (var i in obj.parent.wsagents) { if (obj.parent.wsagents[i].domain.id == domain.id) { domainAgentSessionCount++; } }
376 +
377 + // Check if we have too many user sessions
378 + if (domainAgentSessionCount >= domain.limits.maxagentsessions) { return; } // Too many, hold the connection.
379 + }
380 +
381 // Check that the mesh exists
382 var mesh = obj.parent.meshes[obj.dbMeshKey];
383 if (mesh == null) { console.log('Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddrport + ', ' + obj.dbMeshKey + ').'); return; } // If we disconnect, the agnet will just reconnect. We need to log this or tell agent to connect in a few hours.
meshcentral.js
+1
@@ -437,6 +437,7 @@ function CreateMeshCentralServer(config, args) {
437 var bannedDomains = ['public', 'private', 'images', 'scripts', 'styles', 'views']; // List of banned domains
438 for (i in obj.config.domains) { for (var j in bannedDomains) { if (i == bannedDomains[j]) { console.log("ERROR: Domain '" + i + "' is not allowed domain name in ./data/config.json."); return; } } }
439 for (i in obj.config.domains) {
440 + if (obj.config.domains[i].limits == null) { obj.config.domains[i].limits = {}; }
441 if (obj.config.domains[i].dns == null) { obj.config.domains[i].url = (i == '') ? '/' : ('/' + i + '/'); } else { obj.config.domains[i].url = '/'; }
442 obj.config.domains[i].id = i;
443 if (typeof obj.config.domains[i].userallowedip == 'string') { if (obj.config.domains[i].userallowedip == '') { obj.config.domains[i].userallowedip = null; } else { obj.config.domains[i].userallowedip = obj.config.domains[i].userallowedip.split(','); } }
meshuser.js
+15 -1
@@ -127,6 +127,20 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
127 // Check if the user is logged in
128 if (user == null) { try { obj.ws.close(); } catch (e) { } return; }
129
130 + // Check if we have exceeded the user session limit
131 + if (typeof domain.limits.maxusersessions == 'number') {
132 + // Count the number of user sessions for this domain
133 + var domainUserSessionCount = 0;
134 + for (var i in obj.parent.wssessions2) { if (obj.parent.wssessions2[i].domainid == domain.id) { domainUserSessionCount++; } }
135 +
136 + // Check if we have too many user sessions
137 + if (domainUserSessionCount >= domain.limits.maxusersessions) {
138 + ws.send(JSON.stringify({ action: 'stopped', msg: 'Session count exceed' }));
139 + try { obj.ws.close(); } catch (e) { }
140 + return;
141 + }
142 + }
143 +
144 // Associate this websocket session with the web session
145 obj.ws.userid = req.session.userid;
146 obj.ws.domainid = domain.id;
@@ -643,7 +657,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
657 if (obj.parent.users[newuserid]) break; // Account already exists
658
659 // Check if we exceed the maximum number of user accounts
646 - obj.db.isMaxType(domain.maxaccounts, 'user', function (maxExceed) {
660 + obj.db.isMaxType(domain.limits.maxuseraccounts, 'user', domain.id, function (maxExceed) {
661 if (maxExceed) {
662 // Account count exceed, do notification
663
package.json
+1 -1
@@ -1,6 +1,6 @@
1 {
2 "name": "meshcentral",
3 - "version": "0.2.7-u",
3 + "version": "0.2.7-v",
4 "keywords": [
5 "Remote Management",
6 "Intel AMT",
sample-config.json
+5
@@ -41,6 +41,11 @@
41 "_UserBlockedIP": "127.0.0.1,::1,192.168.0.100",
42 "_AgentAllowedIP": "192.168.0.100/24",
43 "_AgentBlockedIP": "127.0.0.1,::1",
44 + "_Limits": {
45 + "MaxUserAccounts": 100,
46 + "MaxUserSessions": 100,
47 + "MaxAgentSessions": 100
48 + },
49 "_yubikey": { "id": "0000", "secret": "xxxxxxxxxxxxxxxxxxxxx", "_proxy": "http://myproxy.domain.com:80" },
50 },
51 "customer1": {
views/default.handlebars
+11 -3
@@ -855,6 +855,7 @@
855 <script type="text/javascript">
856 'use strict';
857 var args;
858 + var autoReconnect = true;
859 var powerStatetable = ['', 'Powered', 'Sleep', 'Sleep', 'Sleep', 'Hibernating', 'Power off', 'Present'];
860 var StatusStrs = ['Disconnected', 'Connecting...', 'Setup...', 'Connected', 'Intel&reg; AMT Connected'];
861 var sort = 0;
@@ -1076,7 +1077,7 @@
1077 QV('verifyEmailId2', false);
1078 QV('logoutControl', false);
1079 if (errorCode == 'noauth') { QH('p0span', 'Unable to perform authentication'); return; }
1079 - if (prevState == 2) { setTimeout(serverPoll, 5000); } else { QH('p0span', 'Unable to connect web socket'); }
1080 + if (prevState == 2) { if (autoReconnect) { setTimeout(serverPoll, 5000); } } else { QH('p0span', 'Unable to connect web socket'); }
1081 if (authCookieRenewTimer != null) { clearInterval(authCookieRenewTimer); authCookieRenewTimer = null; }
1082 } else if (state == 2) {
1083 // Fetch list of meshes, nodes, files
@@ -1751,7 +1752,8 @@
1752 break;
1753 }
1754 case 'stopped': { // Server is stopping.
1754 - // TODO: Disconnect
1755 + // Disconnect
1756 + console.log(message.msg);
1757 break;
1758 }
1759 default:
@@ -1760,6 +1762,12 @@
1762 }
1763 break;
1764 }
1765 + case 'stopped': { // Server is stopping.
1766 + // Disconnect
1767 + autoReconnect = false;
1768 + QH('p0span', message.msg);
1769 + break;
1770 + }
1771 default:
1772 console.log('Unknown message.action', message.action);
1773 break;
@@ -6379,7 +6387,7 @@
6387 x += addDeviceAttribute('Creation', new Date(user.creation * 1000).toLocaleString());
6388 if (user.login) x += addDeviceAttribute('Last Login', new Date(user.login * 1000).toLocaleString());
6389 var multiFactor = 0;
6382 - if ((user.otpsecret > 0) || (user.otphkeys > 0) || (user.otpkeys > 0)) {
6390 + if ((user.otpsecret > 0) || (user.otphkeys > 0)) {
6391 multiFactor = 1;
6392 var factors = [];
6393 if (user.otpsecret > 0) { factors.push('Authentication App'); }
views/login-mobile.handlebars
+50 -1
@@ -150,7 +150,7 @@
150 <tr>
151 <td align=right width=100>Login token:</td>
152 <td>
153 - <input id=tokenInput type=text name=token maxlength=50 onkeyup=checkToken(event) onkeydown=checkToken(event) />
153 + <input id=tokenInput type=text name=token maxlength=50 onchange=checkToken(event) onkeyup=checkToken(event) onkeydown=checkToken(event) />
154 <input id=hwtokenInput type=text name=hwtoken style="display:none" />
155 </td>
156 </tr>
@@ -163,6 +163,31 @@
163 <hr /><a onclick=xgo(1) style=cursor:pointer>Back to login</a>
164 </form>
165 </div>
166 +
167 + <div id=resettokenpanel style="background-color:#979797;border-radius:16px;width:260px;padding:16px;text-align:center;display:none;clear:both">
168 + <form action=resetaccount method=post autocomplete=off>
169 + <div id=message5>
170 + {{{message}}}
171 + </div>
172 + <table>
173 + <tr>
174 + <td align=right width=100>Login token:</td>
175 + <td>
176 + <input id=resetTokenInput type=text name=token maxlength=50 onchange=resetCheckToken(event) onkeyup=resetCheckToken(event) onkeydown=resetCheckToken(event) />
177 + <input id=resetHwtokenInput type=text name=hwtoken style="display:none" />
178 + </td>
179 + </tr>
180 + <tr>
181 + <td colspan=2>
182 + <div style=float:right><input id=resetTokenOkButton type=submit value="Login" disabled="disabled" /></div>
183 + </td>
184 + </tr>
185 + </table>
186 + <hr /><a onclick=xgo(1) style=cursor:pointer>Back to login</a>
187 + </form>
188 + </div>
189 +
190 +
191 </td>
192 </tr>
193 </table>
@@ -237,6 +262,19 @@
262 }, hardwareKeyChallenge.timeoutSeconds);
263 }
264 }
265 +
266 + if ('{{loginmode}}' == '5') {
267 + try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
268 + if ((hardwareKeyChallenge != null) && u2fSupported()) {
269 + window.u2f.sign(hardwareKeyChallenge.appId, hardwareKeyChallenge.challenge, hardwareKeyChallenge.registeredKeys, function (authResponse) {
270 + if (authResponse.signatureData) {
271 + Q('resetHwtokenInput').value = JSON.stringify(authResponse);
272 + QE('resetTokenOkButton', true);
273 + Q('resetTokenOkButton').click();
274 + }
275 + }, hardwareKeyChallenge.timeoutSeconds);
276 + }
277 + }
278 }
279
280 function showPassHint() {
@@ -246,6 +284,9 @@
284 function xgo(x) {
285 QV('message1', false);
286 QV('message2', false);
287 + QV('message3', false);
288 + QV('message4', false);
289 + QV('message5', false);
290 go(x);
291 }
292
@@ -256,6 +297,7 @@
297 QV('createpanel', x == 2);
298 QV('resetpanel', x == 3);
299 QV('tokenpanel', x == 4);
300 + QV('resettokenpanel', x == 5);
301 if (x == 1) { Q('username').focus(); }
302 if (x == 2) { Q('ausername').focus(); }
303 if (x == 3) { Q('remail').focus(); }
@@ -353,6 +395,13 @@
395 QE('tokenOkButton', (Q('tokenInput').value.length == 6) || (Q('tokenInput').value.length == 8) || (Q('tokenInput').value.length == 44));
396 }
397
398 + function resetCheckToken() {
399 + var t1 = Q('resetTokenInput').value;
400 + var t2 = t1.split(' ').join('');
401 + if (t1 != t2) { Q('resetTokenInput').value = t2; }
402 + QE('resetTokenOkButton', (Q('resetTokenInput').value.length == 6) || (Q('resetTokenInput').value.length == 8) || (Q('resetTokenInput').value.length == 44));
403 + }
404 +
405 //
406 // POPUP DIALOG
407 //
views/login.handlebars
+47 -1
@@ -223,7 +223,7 @@
223 <tr>
224 <td align=right width=100>Login token:</td>
225 <td>
226 - <input id=tokenInput type=text name=token maxlength=50 onkeyup=checkToken(event) onkeydown=checkToken(event) />
226 + <input id=tokenInput type=text name=token maxlength=50 onchange=checkToken(event) onkeyup=checkToken(event) onkeydown=checkToken(event) />
227 <input id=hwtokenInput type=text name=hwtoken style="display:none" />
228 </td>
229 </tr>
@@ -236,6 +236,28 @@
236 <hr /><a onclick=xgo(1) style=cursor:pointer>Back to login</a>
237 </form>
238 </div>
239 + <div id=resettokenpanel style="background-color: #979797;border-radius:16px;width:300px;padding:16px;text-align:center;display:none">
240 + <form action=resetaccount method=post>
241 + <div id=message5>
242 + {{{message}}}
243 + </div>
244 + <table>
245 + <tr>
246 + <td align=right width=100>Login token:</td>
247 + <td>
248 + <input id=resetTokenInput type=text name=token maxlength=50 onchange=resetCheckToken(event) onkeyup=resetCheckToken(event) onkeydown=resetCheckToken(event) />
249 + <input id=resetHwtokenInput type=text name=hwtoken style="display:none" />
250 + </td>
251 + </tr>
252 + <tr>
253 + <td colspan=2>
254 + <div style=float:right><input id=resetTokenOkButton type=submit value="Login" disabled="disabled" /></div>
255 + </td>
256 + </tr>
257 + </table>
258 + <hr /><a onclick=xgo(1) style=cursor:pointer>Back to login</a>
259 + </form>
260 + </div>
261 </td>
262 </tr>
263 </table>
@@ -320,6 +342,19 @@
342 }, hardwareKeyChallenge.timeoutSeconds);
343 }
344 }
345 +
346 + if ('{{loginmode}}' == '5') {
347 + try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
348 + if ((hardwareKeyChallenge != null) && u2fSupported()) {
349 + window.u2f.sign(hardwareKeyChallenge.appId, hardwareKeyChallenge.challenge, hardwareKeyChallenge.registeredKeys, function (authResponse) {
350 + if (authResponse.signatureData) {
351 + Q('resetHwtokenInput').value = JSON.stringify(authResponse);
352 + QE('resetTokenOkButton', true);
353 + Q('resetTokenOkButton').click();
354 + }
355 + }, hardwareKeyChallenge.timeoutSeconds);
356 + }
357 + }
358 }
359
360 function showPassHint() {
@@ -329,6 +364,9 @@
364 function xgo(x) {
365 QV('message1', false);
366 QV('message2', false);
367 + QV('message3', false);
368 + QV('message4', false);
369 + QV('message5', false);
370 go(x);
371 }
372
@@ -339,6 +377,7 @@
377 QV('createpanel', x == 2);
378 QV('resetpanel', x == 3);
379 QV('tokenpanel', x == 4);
380 + QV('resettokenpanel', x == 5);
381 if (x == 1) { Q('username').focus(); }
382 if (x == 2) { Q('ausername').focus(); }
383 if (x == 3) { Q('remail').focus(); }
@@ -448,6 +487,13 @@
487 QE('tokenOkButton', (Q('tokenInput').value.length == 6) || (Q('tokenInput').value.length == 8) || (Q('tokenInput').value.length == 44));
488 }
489
490 + function resetCheckToken() {
491 + var t1 = Q('resetTokenInput').value;
492 + var t2 = t1.split(' ').join('');
493 + if (t1 != t2) { Q('resetTokenInput').value = t2; }
494 + QE('resetTokenOkButton', (Q('resetTokenInput').value.length == 6) || (Q('resetTokenInput').value.length == 8) || (Q('resetTokenInput').value.length == 44));
495 + }
496 +
497 //
498 // POPUP DIALOG
499 //
webserver.js
+67 -19
@@ -320,7 +320,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
320
321 // Return true if this user has 2-step auth active
322 function checkUserOneTimePasswordRequired(domain, user) {
323 - return (user.otpsecret) || (user.otphkeys && (user.otphkeys.length > 0));
323 + return ((user.otpsecret != null) || ((user.otphkeys != null) && (user.otphkeys.length > 0)));
324 }
325
326 // Check the 2-step auth token
@@ -377,7 +377,6 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
377 }
378
379 // Return a U2F hardware key challenge
380 - // TODO: Figure out how to support many U2F keys at the same time.
380 function getHardwareKeyChallenge(req, domain, user, func) {
381 if (req.session.u2fchallenge) { delete req.session.u2fchallenge; };
382 if (user.otphkeys && (user.otphkeys.length > 0)) {
@@ -419,7 +418,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
418 var user = obj.users[userid];
419
420 // Check if this user has 2-step login active
422 - if (checkUserOneTimePasswordRequired(req.domain, user)) {
421 + if (checkUserOneTimePasswordRequired(domain, user)) {
422 checkUserOneTimePassword(req, domain, user, req.body.token, req.body.hwtoken, function (result) {
423 if (result == false) {
424 // 2-step auth is required, but the token is not present or not valid.
@@ -427,7 +426,6 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
426 req.session.loginmode = '4';
427 req.session.tokenusername = xusername;
428 req.session.tokenpassword = xpassword;
430 - req.session.tokenRetry = true;
429 res.redirect(domain.url);
430 } else {
431 // Login succesful
@@ -465,6 +463,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
463 delete req.session.loginmode;
464 delete req.session.tokenusername;
465 delete req.session.tokenpassword;
466 + delete req.session.tokenemail;
467 delete req.session.success;
468 delete req.session.error;
469 delete req.session.passhint;
@@ -503,7 +502,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
502 if ((domain.newaccounts === 0) || (domain.newaccounts === false)) { res.sendStatus(401); return; }
503
504 // Check if we exceed the maximum number of user accounts
506 - obj.db.isMaxType(domain.maxaccounts, 'user', function (maxExceed) {
505 + obj.db.isMaxType(domain.limits.maxuseraccounts, 'user', domain.id, function (maxExceed) {
506 if (maxExceed) {
507 req.session.loginmode = 2;
508 req.session.error = '<b style=color:#8C001A>Account limit reached.</b>';
@@ -569,28 +568,58 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
568 const domain = checkUserIpAddress(req, res);
569 if ((domain == null) || (domain.auth == 'sspi')) return;
570
571 + var email = req.body.email;
572 + if ((email == null) || (email == '')) { email = req.session.tokenemail; }
573 +
574 if ((domain.newaccounts === 0) || (domain.newaccounts === false)) { res.sendStatus(401); return; }
573 - if (!req.body.email || checkEmail(req.body.email) == false) {
575 + if (!email || checkEmail(email) == false) {
576 req.session.loginmode = 3;
577 req.session.error = '<b style=color:#8C001A>Invalid email.</b>';
578 res.redirect(domain.url);
579 } else {
578 - obj.db.GetUserWithVerifiedEmail(domain.id, req.body.email, function (err, docs) {
579 - if (docs.length == 0) {
580 + obj.db.GetUserWithVerifiedEmail(domain.id, email, function (err, docs) {
581 + if ((err != null) || (docs.length == 0)) {
582 req.session.loginmode = 3;
583 req.session.error = '<b style=color:#8C001A>Account not found.</b>';
584 res.redirect(domain.url);
585 } else {
584 - var userFound = docs[0];
585 - if (obj.parent.mailserver != null) {
586 - obj.parent.mailserver.sendAccountResetMail(domain, userFound.name, userFound.email);
587 - req.session.loginmode = 1;
588 - req.session.error = '<b style=color:darkgreen>Hold on, reset mail sent.</b>';
589 - res.redirect(domain.url);
586 + var user = docs[0];
587 + if (checkUserOneTimePasswordRequired(domain, user) == true) {
588 + // Second factor setup, request it now.
589 + checkUserOneTimePassword(req, domain, user, req.body.token, req.body.hwtoken, function (result) {
590 + if (result == false) {
591 + // 2-step auth is required, but the token is not present or not valid.
592 + if ((req.body.token != null) || (req.body.hwtoken != null)) { req.session.error = '<b style=color:#8C001A>Invalid token, try again.</b>'; }
593 + req.session.loginmode = '5';
594 + req.session.tokenemail = email;
595 + res.redirect(domain.url);
596 + } else {
597 + // Send email to perform recovery.
598 + delete req.session.tokenemail;
599 + if (obj.parent.mailserver != null) {
600 + obj.parent.mailserver.sendAccountResetMail(domain, user.name, user.email);
601 + req.session.loginmode = 1;
602 + req.session.error = '<b style=color:darkgreen>Hold on, reset mail sent.</b>';
603 + res.redirect(domain.url);
604 + } else {
605 + req.session.loginmode = 3;
606 + req.session.error = '<b style=color:#8C001A>Unable to sent email.</b>';
607 + res.redirect(domain.url);
608 + }
609 + }
610 + });
611 } else {
591 - req.session.loginmode = 3;
592 - req.session.error = '<b style=color:#8C001A>Unable to sent email.</b>';
593 - res.redirect(domain.url);
612 + // No second factor, send email to perform recovery.
613 + if (obj.parent.mailserver != null) {
614 + obj.parent.mailserver.sendAccountResetMail(domain, user.name, user.email);
615 + req.session.loginmode = 1;
616 + req.session.error = '<b style=color:darkgreen>Hold on, reset mail sent.</b>';
617 + res.redirect(domain.url);
618 + } else {
619 + req.session.loginmode = 3;
620 + req.session.error = '<b style=color:#8C001A>Unable to sent email.</b>';
621 + res.redirect(domain.url);
622 + }
623 }
624 }
625 });
@@ -632,7 +661,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
661 obj.db.SetUser(user);
662
663 // Event the change
635 - obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, { etype: 'user', username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msg: 'Verified email of user ' + EscapeHtml(user.name) + ' (' + EscapeHtml(userinfo.email) + ')', domain: domain.id });
664 + obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, { etype: 'user', username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msg: 'Verified email of user ' + EscapeHtml(user.name) + ' (' + EscapeHtml(user.email) + ')', domain: domain.id });
665
666 // Send the confirmation page
667 res.render(obj.path.join(obj.parent.webViewsPath, 'message'), { title: domain.title, title2: domain.title2, title3: 'Account Verification', message: 'Verified email <b>' + EscapeHtml(user.email) + '</b> for user account <b>' + EscapeHtml(user.name) + '</b>. <a href="' + domain.url + '">Go to login page</a>.' });
@@ -660,7 +689,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
689 userinfo.hash = hash;
690 userinfo.passchange = Math.floor(Date.now() / 1000);
691 userinfo.passhint = null;
663 - delete userinfo.otpsecret; // Currently a email password reset will turn off 2-step login.
692 + //delete userinfo.otpsecret; // Currently a email password reset will turn off 2-step login.
693 obj.db.SetUser(userinfo);
694
695 // Event the change
@@ -920,6 +949,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
949 } else {
950 // Send back the login application
951 // If this is a 2 factor auth request, look for a hardware key challenge.
952 + // Normal login 2 factor request
953 if ((req.session.loginmode == '4') && (req.session.tokenusername)) {
954 var user = obj.users['user/' + domain.id + '/' + req.session.tokenusername];
955 if (user != null) {
@@ -927,6 +957,24 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
957 return;
958 }
959 }
960 + // Password recovery 2 factor request
961 + if ((req.session.loginmode == '5') && (req.session.tokenemail)) {
962 + obj.db.GetUserWithVerifiedEmail(domain.id, req.session.tokenemail, function (err, docs) {
963 + if ((err != null) || (docs.length == 0)) {
964 + req.session = null;
965 + res.redirect(domain.url);
966 + } else {
967 + var user = obj.users[docs[0]._id];
968 + if (user != null) {
969 + getHardwareKeyChallenge(req, domain, user, function (u2fChallenge) { handleRootRequestLogin(req, res, domain, u2fChallenge, passRequirements); });
970 + } else {
971 + req.session = null;
972 + res.redirect(domain.url);
973 + }
974 + }
975 + });
976 + return;
977 + }
978 handleRootRequestLogin(req, res, domain, '', passRequirements);
979 }
980 }