diff --git a/meshcentral-config-schema.json b/meshcentral-config-schema.json index e7c9dc06a8..44e36fb793 100644 --- a/meshcentral-config-schema.json +++ b/meshcentral-config-schema.json @@ -527,6 +527,7 @@ "sms2factor": { "type": "boolean", "default": true, "description": "Set to false to disable SMS 2FA." }, "push2factor": { "type": "boolean", "default": true, "description": "Set to false to disable push notification 2FA." }, "otp2factor": { "type": "boolean", "default": true, "description": "Set to false to disable one-time-password 2FA." }, + "msg2factor": { "type": "boolean", "default": true, "description": "Set to false to disable user messaging 2FA." }, "backupcode2factor": { "type": "boolean", "default": true, "description": "Set to false to disable 2FA backup codes." }, "single2factorWarning": { "type": "boolean", "default": true, "description": "Set to false to disable single 2FA warning." }, "lock2factor": { "type": "boolean", "default": false, "description": "When set to true, prevents any changes to 2FA." }, diff --git a/meshuser.js b/meshuser.js index 7b855f8b14..e0f9666f9a 100644 --- a/meshuser.js +++ b/meshuser.js @@ -1387,6 +1387,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if (command.resetNextLogin === true) { chguser.passchange = -1; } if ((command.consent != null) && (typeof command.consent == 'number')) { if (command.consent == 0) { delete chguser.consent; } else { chguser.consent = command.consent; } change = 1; } if ((command.phone != null) && (typeof command.phone == 'string') && ((command.phone == '') || isPhoneNumber(command.phone))) { if (command.phone == '') { delete chguser.phone; } else { chguser.phone = command.phone; } change = 1; } + if ((command.msghandle != null) && (typeof command.msghandle == 'string')) { if (command.msghandle == '') { delete chguser.msghandle; } else { chguser.msghandle = command.msghandle; } change = 1; } if ((command.flags != null) && (typeof command.flags == 'number')) { // Flags: 1 = Account Image, 2 = Session Recording if ((command.flags == 0) && (chguser.flags != null)) { delete chguser.flags; change = 1; } else { if (command.flags !== chguser.flags) { chguser.flags = command.flags; change = 1; } } @@ -5250,7 +5251,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use 'pong': serverCommandPong, 'powertimeline': serverCommandPowerTimeline, 'print': serverCommandPrint, - 'removePhone': serverCommandremovePhone, + 'removePhone': serverCommandRemovePhone, + 'removeMessaging': serverCommandRemoveMessaging, 'removeuserfromusergroup': serverCommandRemoveUserFromUserGroup, 'report': serverCommandReport, 'serverclearerrorlog': serverCommandServerClearErrorLog, @@ -6304,7 +6306,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use function serverCommandPrint(command) { console.log(command.value); } - function serverCommandremovePhone(command) { + function serverCommandRemovePhone(command) { // Do not allow this command when logged in using a login token if (req.session.loginToken != null) return; @@ -6321,6 +6323,23 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, event); } + function serverCommandRemoveMessaging(command) { + // Do not allow this command when logged in using a login token + if (req.session.loginToken != null) return; + + if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return; // If this account is settings locked, return here. + if (user.msghandle == null) return; + + // Clear the user's phone + delete user.msghandle; + db.SetUser(user); + + // Event the change + var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msgid: 157, msgArgs: [user.name], msg: 'Removed messaging account of user ' + EscapeHtml(user.name), domain: domain.id }; + if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come. + parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, event); + } + function serverCommandRemoveUserFromUserGroup(command) { var err = null; try { diff --git a/public/images/messaging12.png b/public/images/messaging12.png new file mode 100644 index 0000000000..9a5acfeec6 Binary files /dev/null and b/public/images/messaging12.png differ diff --git a/public/images/messaging40.png b/public/images/messaging40.png index a826e90052..f677154976 100644 Binary files a/public/images/messaging40.png and b/public/images/messaging40.png differ diff --git a/views/default.handlebars b/views/default.handlebars index ad714baf05..9b145db556 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -11964,7 +11964,7 @@ x = '
'; x += '
' + "Verified handle" + '
' + EscapeHtml(userinfo.msghandle) + '
'; x += '
'; - setDialogMode(2, "Messaging Notifications", 3, account_managePhoneRemove, x); + setDialogMode(2, "Messaging Notifications", 3, account_manageMessagingRemove, x); account_managePhoneRemoveValidate(); } else { x = '
'; @@ -11985,6 +11985,7 @@ function account_manageMessagingValidate(x) { var ok = (Q('d2handleinput').value.length > 0); QE('idx_dlgOkButton', ok); if ((x == 1) && ok) { dialogclose(1); } } function account_manageMessagingAdd() { if (Q('d2handleinput').value.length == 0) return; QE('d2handleinput', false); meshserver.send({ action: 'verifyMessaging', service: Q('d2serviceselect').value, handle: Q('d2handleinput').value }); } function account_manageMessagingConfirm(b, tag) { meshserver.send({ action: 'confirmMessaging', code: Q('d2phoneCodeInput').value, cookie: tag }); } + function account_manageMessagingRemove() { if (Q('d2delPhone').checked) { meshserver.send({ action: 'removeMessaging' }); } } function account_manageAuthEmail() { if (xxdialogMode || ((features & 0x00800000) == 0)) return; @@ -14616,6 +14617,7 @@ if ((user.otpsecret > 0) || (user.otphkeys > 0) || ((user.otpekey == 1) && (features & 0x00800000)) || ((user.phone != null) && (features & 0x04000000))) { username += ' '; } if (user.phone != null) { username += ' '; } if ((user.siteadmin != null) && ((user.siteadmin & 32) != 0) && (user.siteadmin != 0xFFFFFFFF)) { username += ' '; } + if ((user.msghandle != null) && (features2 & 0x02000000)) { username += ' '; } x += '
'; x += '
'; x += '
'; @@ -15686,7 +15688,7 @@ } if ((features2 & 0x02000000) || (user.msghandle != null)) { // If user messaging is enabled on the server or user has a messaging handle - x += addDeviceAttribute("Messaging", (user.msghandle?user.msghandle:('' + "None" + '')) + ' '); + x += addDeviceAttribute("Messaging", ' ' + (user.msghandle?user.msghandle:('' + "None" + '')) + ' '); } // Display features @@ -15757,6 +15759,7 @@ if (user.otpkeys > 0) { factors.push("Backup Codes"); } if (user.otpdev > 0) { factors.push("Device Push"); } if ((user.phone != null) && (features & 0x04000000)) { factors.push("SMS"); } + if ((user.msghandle != null) && (features2 & 0x04000000)) { factors.push("Messaging"); } x += addDeviceAttribute("Security", ' ' + factors.join(', ')); } @@ -15831,14 +15834,32 @@ p30editPhoneValidate(); } - function p30editMessaging() { // TODO + function p30editMessaging() { if (xxdialogMode) return; - var x = '
'; - x += '' + "SMS capable phone number for this user." + '
' + "Leave blank for none."; - x += '

' + "Phone number:" + '
'; - setDialogMode(2, "Phone Notifications", 3, p30editPhoneEx, x, 'verifyPhone'); - Q('d2phoneinput').focus(); - p30editPhoneValidate(); + var x = '
'; + x += '' + "Messaging account for this user." + '
' + "Leave blank for none."; + + var y = ''; + x += '
' + "Service" + '' + y; + x += '
' + "Handle" + ''; + x += '
'; + + setDialogMode(2, "Messaging Notifications", 3, p30editMessagingEx, x, 'verifyMessaging'); + Q('d2handleinput').focus(); + p30editMessagingValidate(); + } + + function p30editMessagingValidate(x) { if (x == 1) { dialogclose(1); } } + + // Send to the server the user's messaging account + function p30editMessagingEx() { + var handle = null; + if (Q('d2handleinput').value == '') { handle = ''; } + else if (Q('d2serviceselect').value == 1) { handle = 'telegram:@' + Q('d2handleinput').value; } + if (handle != null) { meshserver.send({ action: 'edituser', id: currentUser._id, msghandle: handle }); } } function p20edituserfeatures() {