More work on login tokens.

Ylian Saint-Hilaire committed Apr 15, 2021 at 18:32 UTC 08b8ee28c63727f49977c828a1fa87239db5f9f6
2 files changed +86 -5
meshuser.js
+57 -2
@@ -5610,12 +5610,67 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
5610 }
5611 break;
5612 }
5613 - case 'createLoginToken': {
5613 + case 'loginTokens': { // Respond with the list of currently valid login tokens
5614 + parent.db.GetAllTypeNodeFiltered(['logintoken-' + user._id], domain.id, 'logintoken', null, function (err, docs) {
5615 + if (err != null) return;
5616 + var now = Date.now(), removed = 0, okDocs = [];
5617 + for (var i = 0; i < docs.length; i++) {
5618 + const doc = docs[i];
5619 + if (doc.expireTime < now) {
5620 + // This share is expired.
5621 + parent.db.Remove(doc._id, function () { }); delete docs[i]; removed++;
5622 + } else {
5623 + // This share is ok, remove extra data we don't need to send.
5624 + delete doc._id; delete doc.domain; delete doc.nodeid; delete doc.type; delete doc.userid; delete doc.salt; delete doc.hash;
5625 + okDocs.push(doc);
5626 + }
5627 + }
5628 + try { ws.send(JSON.stringify({ action: 'loginTokens', loginTokens: okDocs })); } catch (ex) { }
5629 +
5630 + // If any login tokens where removed, event the change.
5631 + if (removed > 0) {
5632 + // Dispatch the new event
5633 + var targets = ['*', 'server-users', user._id];
5634 + if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
5635 + var event = { etype: 'user', userid: user._id, username: user.name, action: 'loginTokenChanged', domain: domain.id, loginTokens: okDocs, nolog: 1 };
5636 + parent.parent.DispatchEvent(targets, obj, event);
5637 + }
5638 + });
5639 + break;
5640 + }
5641 + case 'createLoginToken': { // Create a new login token
5642 if ((typeof domain.passwordrequirements != 'object') && (domain.passwordrequirements.logintokens == false)) break; // Login tokens are not supported on this server
5643 if (common.validateString(command.name, 1, 100) == false) break; // Check name
5644 if ((typeof command.expire != 'number') || (command.expire < 0)) break; // Check expire
5645
5618 - console.log(command);
5646 + // Generate a token username. Don't have any + or / in the username or password
5647 + var tokenUser = '~t:' + Buffer.from(parent.parent.crypto.randomBytes(12), 'binary').toString('base64');
5648 + while ((tokenUser.indexOf('+') >= 0) || (tokenUser.indexOf('/') >= 0)) { tokenUser = '~t:' + Buffer.from(parent.parent.crypto.randomBytes(12), 'binary').toString('base64'); };
5649 + var tokenPass = Buffer.from(parent.parent.crypto.randomBytes(15), 'binary').toString('base64');
5650 + while ((tokenPass.indexOf('+') >= 0) || (tokenPass.indexOf('/') >= 0)) { tokenPass = Buffer.from(parent.parent.crypto.randomBytes(15), 'binary').toString('base64'); };
5651 +
5652 + // Create a user, generate a salt and hash the password
5653 + require('./pass').hash(tokenPass, function (err, salt, hash, tag) {
5654 + if (err) throw err;
5655 +
5656 + // Compute expire time
5657 + const created = Date.now();
5658 + var expire = 0;
5659 + if (command.expire > 0) { expire = created + (command.expire * 60000); }
5660 +
5661 + // Generate the token password
5662 + const dbentry = { _id: 'logintoken-' + tokenUser, type: 'logintoken', nodeid: 'logintoken-' + user._id, userid: user._id, name: command.name, tokenUser: tokenUser, salt: salt, hash: hash, domain: domain.id, created: created, expire: expire };
5663 + parent.db.Set(dbentry);
5664 +
5665 + // Send the token information back
5666 + try { ws.send(JSON.stringify({ action: 'createLoginToken', name: command.name, tokenUser: tokenUser, tokenPass: tokenPass, created: created, expire: expire })); } catch (ex) { }
5667 +
5668 + // Dispatch the new event
5669 + var targets = ['*', 'server-users', user._id];
5670 + if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
5671 + var event = { etype: 'user', userid: user._id, username: user.name, action: 'loginTokenAdded', msgid: 115, msg: "Added login token", domain: domain.id };
5672 + parent.parent.DispatchEvent(targets, obj, event);
5673 + });
5674
5675 break;
5676 }
views/default.handlebars
+29 -3
@@ -1293,6 +1293,7 @@
1293 var showRealNames = false;
1294 var meshserver = null;
1295 var meshes = {};
1296 + var loginTokens = {};
1297 var meshcount = 0;
1298 var nodes = null;
1299 var usergroups = null;
@@ -1895,6 +1896,7 @@
1896 meshserver.send({ action: 'usergroups' });
1897 meshserver.send({ action: 'meshes' });
1898 meshserver.send({ action: 'nodes', id: '{{currentNode}}' });
1899 + meshserver.send({ action: 'loginTokens' });
1900 if (pluginHandler != null) { meshserver.send({ action: 'plugins' }); }
1901 if ('{{currentNode}}'.toLowerCase() == '') { meshserver.send({ action: 'files' }); }
1902 if ('{{viewmode}}'.toLowerCase() == '') { go(1); }
@@ -3167,6 +3169,13 @@
3169 if ((Q('DevFilterSelect').value == 2) || (Q('DevFilterSelect').value == 6)) { mainUpdate(1); }
3170 break;
3171 }
3172 + case 'loginTokenChanged': {
3173 + if (message.event.userid != userinfo._id) return;
3174 + loginTokens = message.event.loginTokens;
3175 + // TODO: Update
3176 + console.log('t1', loginTokens);
3177 + break;
3178 + }
3179 case 'stopped': { // Server is stopping.
3180 // Disconnect
3181 //console.log(message.msg);
@@ -3329,11 +3338,26 @@
3338 }
3339 break;
3340 }
3332 - case 'getDeviceDetails':{
3333 - console.log(message);
3341 + case 'getDeviceDetails': {
3342 saveAs(new Blob([message.data], { type: 'application/octet-stream' }), "devicelist" + '.' + message.type);
3343 break;
3344 }
3345 + case 'createLoginToken': {
3346 + if (xxdialogMode) return;
3347 + var x = "Take note of this username and password, the password cannot be shown again." + '<br /><br />';
3348 + x += addHtmlValue("Name", EscapeHtml(message.name))
3349 + if (message.expire != 0) { x += addHtmlValue("Expire", EscapeHtml(printDateTime(new Date(message.expire)))); }
3350 + x += addHtmlValue("Username", '<img src="images/link4.png" title="' + "Copy link to clipboard" + '" style="margin:9px;cursor:pointer;float:right" onclick=copyTextToClip2("' + encodeURIComponentEx(message.tokenUser) + '") width=10 height=10><div class=selecttext style=width:230px;padding:5px;background-color:orange;border-radius:5px;font-size:15px>' + EscapeHtml(message.tokenUser) + '</div>');
3351 + x += addHtmlValue("Password", '<img src="images/link4.png" title="' + "Copy link to clipboard" + '" style="margin:9px;cursor:pointer;float:right" onclick=copyTextToClip2("' + encodeURIComponentEx(message.tokenPass) + '") width=10 height=10><div class=selecttext style=width:230px;padding:5px;background-color:orange;border-radius:5px;font-size:15px>' + EscapeHtml(message.tokenPass) + '</div>');
3352 + setDialogMode(2, "Create Login Token", 1, null, x);
3353 + break;
3354 + }
3355 + case 'loginTokens': {
3356 + loginTokens = message.loginTokens;
3357 + // TODO: Update
3358 + console.log('t2', loginTokens);
3359 + break;
3360 + }
3361 default:
3362 //console.log('Unknown message.action', message.action);
3363 break;
@@ -12012,7 +12036,9 @@
12036 111: "Device requested Intel(R) AMT ACM TLS activation, FQDN: {0}",
12037 112: "Ended messenger session \"{0}\" from {1} to {2}, {3} second(s)",
12038 113: "Added push notification authentication device",
12015 - 114: "Removed push notification authentication device"
12039 + 114: "Removed push notification authentication device",
12040 + 115: "Added login token",
12041 + 116: "Removed login token"
12042 };
12043
12044 // Highlights the device being hovered