Added login token support to MeshCtrl.js

Ylian Saint-Hilaire committed May 7, 2021 at 12:15 UTC acdea410c32ff245d4a6be33374b4bbe21dc8082
2 files changed +72 -5
meshctrl.js
+60 -1
@@ -7,7 +7,7 @@ try { require('ws'); } catch (ex) { console.log('Missing module "ws", type "npm
7 var settings = {};
8 const crypto = require('crypto');
9 const args = require('minimist')(process.argv.slice(2));
10 -const possibleCommands = ['edituser', 'listusers', 'listusersessions', 'listdevicegroups', 'listdevices', 'listusersofdevicegroup', 'listevents', 'serverinfo', 'userinfo', 'adduser', 'removeuser', 'adddevicegroup', 'removedevicegroup', 'editdevicegroup', 'broadcast', 'showevents', 'addusertodevicegroup', 'removeuserfromdevicegroup', 'addusertodevice', 'removeuserfromdevice', 'sendinviteemail', 'generateinvitelink', 'config', 'movetodevicegroup', 'deviceinfo', 'addusergroup', 'listusergroups', 'removeusergroup', 'runcommand', 'shell', 'upload', 'download', 'deviceopenurl', 'devicemessage', 'devicetoast', 'addtousergroup', 'removefromusergroup', 'removeallusersfromusergroup', 'devicesharing', 'devicepower', 'indexagenterrorlog'];
10 +const possibleCommands = ['edituser', 'listusers', 'listusersessions', 'listdevicegroups', 'listdevices', 'listusersofdevicegroup', 'listevents', 'logintokens', 'serverinfo', 'userinfo', 'adduser', 'removeuser', 'adddevicegroup', 'removedevicegroup', 'editdevicegroup', 'broadcast', 'showevents', 'addusertodevicegroup', 'removeuserfromdevicegroup', 'addusertodevice', 'removeuserfromdevice', 'sendinviteemail', 'generateinvitelink', 'config', 'movetodevicegroup', 'deviceinfo', 'addusergroup', 'listusergroups', 'removeusergroup', 'runcommand', 'shell', 'upload', 'download', 'deviceopenurl', 'devicemessage', 'devicetoast', 'addtousergroup', 'removefromusergroup', 'removeallusersfromusergroup', 'devicesharing', 'devicepower', 'indexagenterrorlog'];
11 if (args.proxy != null) { try { require('https-proxy-agent'); } catch (ex) { console.log('Missing module "https-proxy-agent", type "npm install https-proxy-agent" to install it.'); return; } }
12
13 if (args['_'].length == 0) {
@@ -25,6 +25,7 @@ if (args['_'].length == 0) {
25 console.log(" ListDeviceGroups - List device groups.");
26 console.log(" ListUsersOfDeviceGroup - List the users in a device group.");
27 console.log(" ListEvents - List server events.");
28 + console.log(" LoginTokens - List, create and remove login tokens.");
29 console.log(" DeviceInfo - Show information about a device.");
30 console.log(" Config - Perform operation on config.json file.");
31 console.log(" AddUser - Create a new user account.");
@@ -84,6 +85,7 @@ if (args['_'].length == 0) {
85 case 'listdevicegroups': { ok = true; break; }
86 case 'listdevices': { ok = true; break; }
87 case 'listevents': { ok = true; break; }
88 + case 'logintokens': { ok = true; break; }
89 case 'listusersofdevicegroup': {
90 if (args.id == null) { console.log(winRemoveSingleQuotes("Missing group id, use --id '[groupid]'")); }
91 else { ok = true; }
@@ -396,6 +398,16 @@ if (args['_'].length == 0) {
398 console.log(" --json - Give results in JSON format.");
399 break;
400 }
401 + case 'logintokens': {
402 + console.log("List account login tokens and allow addition and removal. Example usage:\r\n");
403 + console.log(" MeshCtrl LoginTokens ");
404 + console.log("\r\nOptional arguments:\r\n");
405 + console.log(" --remove [name] - Remove a login token.");
406 + console.log(" --add [name] - Add a login token.");
407 + console.log(" --expire [minutes] - When adding a token, minutes until expire.");
408 + console.log(" --json - Show login tokens in JSON format.");
409 + break;
410 + }
411 case 'adduser': {
412 console.log("Add a new user account. Example usages:\r\n");
413 console.log(" MeshCtrl AddUser --user newaccountname --pass newpassword");
@@ -1091,6 +1103,18 @@ function serverConnect() {
1103 ws.send(JSON.stringify(cmd));
1104 break;
1105 }
1106 + case 'logintokens': {
1107 + if (args.add) {
1108 + var cmd = { action: 'createLoginToken', name: args.add, expire: 0, responseid: 'meshctrl' };
1109 + if (args.expire) { cmd.expire = parseInt(args.expire); }
1110 + ws.send(JSON.stringify(cmd));
1111 + } else {
1112 + var cmd = { action: 'loginTokens', responseid: 'meshctrl' };
1113 + if (args.remove) { cmd.remove = [args.remove]; }
1114 + ws.send(JSON.stringify(cmd));
1115 + }
1116 + break;
1117 + }
1118 case 'adduser': {
1119 var siteadmin = getSiteAdminRights(args);
1120 if (args.randompass) { args.pass = getRandomAmtPassword(); }
@@ -1939,6 +1963,34 @@ function serverConnect() {
1963 process.exit();
1964 break;
1965 }
1966 + case 'createLoginToken': {
1967 + if (data.result != null) {
1968 + console.log(data.result);
1969 + process.exit();
1970 + } else {
1971 + ws.send(JSON.stringify({ action: 'loginTokens', responseid: 'meshctrl' }));
1972 + }
1973 + break;
1974 + }
1975 + case 'loginTokens': {
1976 + if (args.json) {
1977 + console.log(data.loginTokens);
1978 + } else {
1979 + console.log("Name Username Expire");
1980 + console.log("-------------------------------------------------------------------------------------");
1981 + if (data.loginTokens.length == 0) {
1982 + console.log("No login tokens");
1983 + } else {
1984 + for (var i in data.loginTokens) {
1985 + var t = data.loginTokens[i];
1986 + var e = (t.expire == 0) ? "Unlimited" : new Date(t.expire).toLocaleString();
1987 + console.log(padString(t.name, 28) + padString(t.tokenUser, 28) + e);
1988 + }
1989 + }
1990 + }
1991 + process.exit();
1992 + break;
1993 + }
1994 default: { break; }
1995 }
1996 //console.log('Data', data);
@@ -1946,6 +1998,13 @@ function serverConnect() {
1998 });
1999 }
2000
2001 +// String padding function
2002 +
2003 +function padString(str, pad) {
2004 + var xpad = ' ';
2005 + if (str.length >= pad) return str; return str + xpad.substring(0, pad - str.length)
2006 +}
2007 +
2008 // Connect tunnel to a remote agent
2009 function connectTunnel(url) {
2010 // Setup WebSocket options
meshuser.js
+12 -4
@@ -5796,10 +5796,18 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
5796 break;
5797 }
5798 case 'createLoginToken': { // Create a new login token
5799 - if (req.session.loginToken != null) break; // Do not allow this command when logged in using a login token
5800 - if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.logintokens == false)) break; // Login tokens are not supported on this server
5801 - if (common.validateString(command.name, 1, 100) == false) break; // Check name
5802 - if ((typeof command.expire != 'number') || (command.expire < 0)) break; // Check expire
5799 + var err = null;
5800 +
5801 + if (req.session.loginToken != null) { err = "Access denied"; } // Do not allow this command when logged in using a login token
5802 + else if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.logintokens == false)) { err = "Not supported"; } // Login tokens are not supported on this server
5803 + else if (common.validateString(command.name, 1, 100) == false) { err = "Invalid name"; } // Check name
5804 + else if ((typeof command.expire != 'number') || (command.expire < 0)) { err = "Invalid expire value"; } // Check expire
5805 +
5806 + // Handle any errors
5807 + if (err != null) {
5808 + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'createLoginToken', responseid: command.responseid, result: err })); } catch (ex) { } }
5809 + break;
5810 + }
5811
5812 // Generate a token username. Don't have any + or / in the username or password
5813 var tokenUser = '~t:' + Buffer.from(parent.parent.crypto.randomBytes(12), 'binary').toString('base64');