diff --git a/meshctrl.js b/meshctrl.js index 26d9fa0a..20824ef1 100644 --- a/meshctrl.js +++ b/meshctrl.js @@ -7,7 +7,7 @@ try { require('ws'); } catch (ex) { console.log('Missing module "ws", type "npm var settings = {}; const crypto = require('crypto'); const args = require('minimist')(process.argv.slice(2)); -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']; +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']; 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; } } if (args['_'].length == 0) { @@ -25,6 +25,7 @@ if (args['_'].length == 0) { console.log(" ListDeviceGroups - List device groups."); console.log(" ListUsersOfDeviceGroup - List the users in a device group."); console.log(" ListEvents - List server events."); + console.log(" LoginTokens - List, create and remove login tokens."); console.log(" DeviceInfo - Show information about a device."); console.log(" Config - Perform operation on config.json file."); console.log(" AddUser - Create a new user account."); @@ -84,6 +85,7 @@ if (args['_'].length == 0) { case 'listdevicegroups': { ok = true; break; } case 'listdevices': { ok = true; break; } case 'listevents': { ok = true; break; } + case 'logintokens': { ok = true; break; } case 'listusersofdevicegroup': { if (args.id == null) { console.log(winRemoveSingleQuotes("Missing group id, use --id '[groupid]'")); } else { ok = true; } @@ -396,6 +398,16 @@ if (args['_'].length == 0) { console.log(" --json - Give results in JSON format."); break; } + case 'logintokens': { + console.log("List account login tokens and allow addition and removal. Example usage:\r\n"); + console.log(" MeshCtrl LoginTokens "); + console.log("\r\nOptional arguments:\r\n"); + console.log(" --remove [name] - Remove a login token."); + console.log(" --add [name] - Add a login token."); + console.log(" --expire [minutes] - When adding a token, minutes until expire."); + console.log(" --json - Show login tokens in JSON format."); + break; + } case 'adduser': { console.log("Add a new user account. Example usages:\r\n"); console.log(" MeshCtrl AddUser --user newaccountname --pass newpassword"); @@ -1091,6 +1103,18 @@ function serverConnect() { ws.send(JSON.stringify(cmd)); break; } + case 'logintokens': { + if (args.add) { + var cmd = { action: 'createLoginToken', name: args.add, expire: 0, responseid: 'meshctrl' }; + if (args.expire) { cmd.expire = parseInt(args.expire); } + ws.send(JSON.stringify(cmd)); + } else { + var cmd = { action: 'loginTokens', responseid: 'meshctrl' }; + if (args.remove) { cmd.remove = [args.remove]; } + ws.send(JSON.stringify(cmd)); + } + break; + } case 'adduser': { var siteadmin = getSiteAdminRights(args); if (args.randompass) { args.pass = getRandomAmtPassword(); } @@ -1939,6 +1963,34 @@ function serverConnect() { process.exit(); break; } + case 'createLoginToken': { + if (data.result != null) { + console.log(data.result); + process.exit(); + } else { + ws.send(JSON.stringify({ action: 'loginTokens', responseid: 'meshctrl' })); + } + break; + } + case 'loginTokens': { + if (args.json) { + console.log(data.loginTokens); + } else { + console.log("Name Username Expire"); + console.log("-------------------------------------------------------------------------------------"); + if (data.loginTokens.length == 0) { + console.log("No login tokens"); + } else { + for (var i in data.loginTokens) { + var t = data.loginTokens[i]; + var e = (t.expire == 0) ? "Unlimited" : new Date(t.expire).toLocaleString(); + console.log(padString(t.name, 28) + padString(t.tokenUser, 28) + e); + } + } + } + process.exit(); + break; + } default: { break; } } //console.log('Data', data); @@ -1946,6 +1998,13 @@ function serverConnect() { }); } +// String padding function + +function padString(str, pad) { + var xpad = ' '; + if (str.length >= pad) return str; return str + xpad.substring(0, pad - str.length) +} + // Connect tunnel to a remote agent function connectTunnel(url) { // Setup WebSocket options diff --git a/meshuser.js b/meshuser.js index c8532769..a306652a 100644 --- a/meshuser.js +++ b/meshuser.js @@ -5796,10 +5796,18 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use break; } case 'createLoginToken': { // Create a new login token - if (req.session.loginToken != null) break; // Do not allow this command when logged in using a login token - if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.logintokens == false)) break; // Login tokens are not supported on this server - if (common.validateString(command.name, 1, 100) == false) break; // Check name - if ((typeof command.expire != 'number') || (command.expire < 0)) break; // Check expire + var err = null; + + if (req.session.loginToken != null) { err = "Access denied"; } // Do not allow this command when logged in using a login token + else if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.logintokens == false)) { err = "Not supported"; } // Login tokens are not supported on this server + else if (common.validateString(command.name, 1, 100) == false) { err = "Invalid name"; } // Check name + else if ((typeof command.expire != 'number') || (command.expire < 0)) { err = "Invalid expire value"; } // Check expire + + // Handle any errors + if (err != null) { + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'createLoginToken', responseid: command.responseid, result: err })); } catch (ex) { } } + break; + } // Generate a token username. Don't have any + or / in the username or password var tokenUser = '~t:' + Buffer.from(parent.parent.crypto.randomBytes(12), 'binary').toString('base64');