First pass at adding Telegram support (#4650)
Ylian Saint-Hilaire committed
Oct 22, 2022 at 07:23 UTC
7e3dce0ef7c02cb213e5842992d7eb081e50f101
8 files changed
+394
-25
meshcentral-config-schema.json
+41
-10
@@ -1299,13 +1299,16 @@
1299
"required": [ "host", "port", "from", "tls" ]
1300
},
1301
"sms": {
1302
- "title" : "SMS provider",
1302
+ "title": "SMS provider",
1303
"description": "Connects MeshCentral to a SMS text messaging provider, allows MeshCentral to send SMS messages for 2FA or user notification.",
1304
"oneOf": [
1305
{
1306
- "type": "object",
1306
+ "type": "object",
1307
"properties": {
1308
- "provider": { "type": "string", "enum": [ "twilio" ] },
1308
+ "provider": {
1309
+ "type": "string",
1310
+ "enum": [ "twilio" ]
1311
+ },
1312
"sid": { "type": "string" },
1313
"auth": { "type": "string" },
1314
"from": { "type": "string" }
@@ -1313,9 +1316,12 @@
1316
"required": [ "provider", "sid", "auth", "from" ]
1317
},
1318
{
1316
- "type": "object",
1319
+ "type": "object",
1320
"properties": {
1318
- "provider": { "type": "string", "enum": [ "plivo" ] },
1321
+ "provider": {
1322
+ "type": "string",
1323
+ "enum": [ "plivo" ]
1324
+ },
1325
"id": { "type": "string" },
1326
"token": { "type": "string" },
1327
"from": { "type": "string" }
@@ -1323,23 +1329,48 @@
1329
"required": [ "provider", "id", "token", "from" ]
1330
},
1331
{
1326
- "type": "object",
1332
+ "type": "object",
1333
"properties": {
1328
- "provider": { "type": "string", "enum": [ "telnyx" ] },
1334
+ "provider": {
1335
+ "type": "string",
1336
+ "enum": [ "telnyx" ]
1337
+ },
1338
"apikey": { "type": "string" },
1339
"from": { "type": "string" }
1340
},
1341
"required": [ "provider", "apikey", "from" ]
1342
},
1343
{
1335
- "type": "object",
1344
+ "type": "object",
1345
"properties": {
1337
- "provider": { "type": "string", "enum": [ "url" ] },
1338
- "url": { "type": "string", "description": "A http or https URL with {{phone}} and {{message}} in the string. These will be replaced with the URL encoded target phone number and message." }
1346
+ "provider": {
1347
+ "type": "string",
1348
+ "enum": [ "url" ]
1349
+ },
1350
+ "url": {
1351
+ "type": "string",
1352
+ "description": "A http or https URL with {{phone}} and {{message}} in the string. These will be replaced with the URL encoded target phone number and message."
1353
+ }
1354
},
1355
"required": [ "url" ]
1356
}
1357
]
1358
+ },
1359
+ "messaging": {
1360
+ "title" : "Messaging server",
1361
+ "description": "This section allow MeshCentral to send messages over user messaging networks like Telegram",
1362
+ "type": "object",
1363
+ "properties": {
1364
+ "telegram": {
1365
+ "type": "object",
1366
+ "description": "Configure Telegram messaging system",
1367
+ "properties": {
1368
+ "apiid": { "type": "number" },
1369
+ "apihash": { "type": "string" },
1370
+ "session": { "type": "string" }
1371
+ }
1372
+ }
1373
+ }
1374
}
1375
},
1376
"required": [ "settings", "domains" ]
meshcentral.js
+26
-13
@@ -22,19 +22,20 @@ if (process.argv[2] == '--launch') { try { require('appmetrics-dash').monitor({
22
function CreateMeshCentralServer(config, args) {
23
const obj = {};
24
obj.db = null;
25
- obj.webserver = null;
26
- obj.redirserver = null;
27
- obj.mpsserver = null;
28
- obj.mqttbroker = null;
29
- obj.swarmserver = null;
30
- obj.smsserver = null;
25
+ obj.webserver = null; // HTTPS main web server, typically on port 443
26
+ obj.redirserver = null; // HTTP relay web server, typically on port 80
27
+ obj.mpsserver = null; // Intel AMT CIRA server, typically on port 4433
28
+ obj.mqttbroker = null; // MQTT server, not is not often used
29
+ obj.swarmserver = null; // Swarm server, this is used only to update older MeshCentral v1 agents
30
+ obj.smsserver = null; // SMS server, used to send user SMS messages
31
+ obj.msgserver = null; // Messaging server, used to sent used messages
32
obj.amtEventHandler = null;
33
obj.pluginHandler = null;
34
obj.amtScanner = null;
34
- obj.amtManager = null;
35
+ obj.amtManager = null; // Intel AMT manager, used to oversee all Intel AMT devices, activate them and sync policies
36
obj.meshScanner = null;
37
obj.taskManager = null;
37
- obj.letsencrypt = null;
38
+ obj.letsencrypt = null; // Let's encrypt server, used to get and renew TLS certificates
39
obj.eventsDispatch = {};
40
obj.fs = require('fs');
41
obj.path = require('path');
@@ -758,7 +759,7 @@ function CreateMeshCentralServer(config, args) {
759
}
760
761
// Check top level configuration for any unrecognized values
761
- if (config) { for (var i in config) { if ((typeof i == 'string') && (i.length > 0) && (i[0] != '_') && (['settings', 'domaindefaults', 'domains', 'configfiles', 'smtp', 'letsencrypt', 'peers', 'sms', 'sendgrid', 'sendmail', 'firebase', 'firebaserelay', '$schema'].indexOf(i) == -1)) { addServerWarning('Unrecognized configuration option \"' + i + '\".', 3, [ i ]); } } }
762
+ if (config) { for (var i in config) { if ((typeof i == 'string') && (i.length > 0) && (i[0] != '_') && (['settings', 'domaindefaults', 'domains', 'configfiles', 'smtp', 'letsencrypt', 'peers', 'sms', 'messaging', 'sendgrid', 'sendmail', 'firebase', 'firebaserelay', '$schema'].indexOf(i) == -1)) { addServerWarning('Unrecognized configuration option \"' + i + '\".', 3, [ i ]); } } }
763
764
// Read IP lists from files if applicable
765
config.settings.userallowedip = obj.args.userallowedip = readIpListFromFile(obj.args.userallowedip);
@@ -858,7 +859,7 @@ function CreateMeshCentralServer(config, args) {
859
if (err != null) { console.log("Database error: " + err); process.exit(); return; }
860
if ((docs == null) || (docs.length == 0)) { console.log("Unknown userid, usage: --resetaccount [userid] --domain (domain) --pass [password]."); process.exit(); return; }
861
const user = docs[0]; if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { user.siteadmin -= 32; } // Unlock the account.
861
- delete user.phone; delete user.otpekey; delete user.otpsecret; delete user.otpkeys; delete user.otphkeys; delete user.otpdev; delete user.otpsms; // Disable 2FA
862
+ delete user.phone; delete user.otpekey; delete user.otpsecret; delete user.otpkeys; delete user.otphkeys; delete user.otpdev; delete user.otpsms; delete user.otpmsg; // Disable 2FA
863
if (obj.args.hashpass) {
864
// Reset an account using a pre-hashed password. Use --hashpassword to pre-hash a password.
865
var hashpasssplit = obj.args.hashpass.split(',');
@@ -1777,6 +1778,11 @@ function CreateMeshCentralServer(config, args) {
1778
if ((obj.smsserver != null) && (obj.args.lanonly == true)) { addServerWarning("SMS gateway has limited use in LAN mode.", 19); }
1779
}
1780
1781
+ // Setup user messaging
1782
+ if (config.messaging != null) {
1783
+ obj.msgserver = require('./meshmessaging.js').CreateServer(obj);
1784
+ }
1785
+
1786
// Setup web based push notifications
1787
if ((typeof config.settings.webpush == 'object') && (typeof config.settings.webpush.email == 'string')) {
1788
obj.webpush = require('web-push');
@@ -4008,9 +4014,16 @@ function mainStart() {
4014
if (config.settings.desktopmultiplex === true) { modules.push('image-size'); }
4015
4016
// SMS support
4011
- if ((config.sms != null) && (config.sms.provider == 'twilio')) { modules.push('twilio'); }
4012
- if ((config.sms != null) && (config.sms.provider == 'plivo')) { modules.push('plivo'); }
4013
- if ((config.sms != null) && (config.sms.provider == 'telnyx')) { modules.push('telnyx'); }
4017
+ if (config.sms != null) {
4018
+ if (config.sms.provider == 'twilio') { modules.push('twilio'); }
4019
+ if (config.sms.provider == 'plivo') { modules.push('plivo'); }
4020
+ if (config.sms.provider == 'telnyx') { modules.push('telnyx'); }
4021
+ }
4022
+
4023
+ // Messaging support
4024
+ if (config.messaging != null) {
4025
+ if (config.messaging.telegram != null) { modules.push('telegram'); modules.push('input'); }
4026
+ }
4027
4028
// Setup web based push notifications
4029
if ((typeof config.settings.webpush == 'object') && (typeof config.settings.webpush.email == 'string')) { modules.push('web-push'); }
meshmessaging.js
new
+151
@@ -0,0 +1,151 @@
1
+/**
2
+* @description MeshCentral user messaging communication module
3
+* @author Ylian Saint-Hilaire
4
+* @copyright Intel Corporation 2022
5
+* @license Apache-2.0
6
+* @version v0.0.1
7
+*/
8
+
9
+/*xjslint node: true */
10
+/*xjslint plusplus: true */
11
+/*xjslint maxlen: 256 */
12
+/*jshint node: true */
13
+/*jshint strict: false */
14
+/*jshint esversion: 6 */
15
+"use strict";
16
+
17
+/*
18
+// For Telegram, add this in config.json
19
+"messaging": {
20
+ "telegram": {
21
+ "apiid": 00000000,
22
+ "apihash": "00000000000000000000000",
23
+ "session": "aaaaaaaaaaaaaaaaaaaaaaa"
24
+ }
25
+}
26
+*/
27
+
28
+// Construct a SMS server object
29
+module.exports.CreateServer = function (parent) {
30
+ var obj = {};
31
+ obj.parent = parent;
32
+ obj.providers = 0; // 1 = Telegram, 2 = Signal
33
+ obj.telegramClient = null;
34
+
35
+ // Messaging client setup
36
+ if (parent.config.messaging.telegram) {
37
+ // Validate Telegram configuration values
38
+ var telegramOK = true;
39
+ if (typeof parent.config.messaging.telegram.apiid != 'number') { console.log('Invalid or missing Telegram apiid.'); telegramOK = false; }
40
+ if (typeof parent.config.messaging.telegram.apihash != 'string') { console.log('Invalid or missing Telegram apihash.'); telegramOK = false; }
41
+ if (typeof parent.config.messaging.telegram.session != 'string') { console.log('Invalid or missing Telegram session.'); telegramOK = false; }
42
+
43
+ if (telegramOK) {
44
+ // Setup Telegram
45
+ async function setupTelegram() {
46
+ const { TelegramClient } = require('telegram');
47
+ const { StringSession } = require('telegram/sessions');
48
+ const input = require('input');
49
+ const stringSession = new StringSession(parent.config.messaging.telegram.session);
50
+ const client = new TelegramClient(stringSession, parent.config.messaging.telegram.apiid, parent.config.messaging.telegram.apihash, { connectionRetries: 5 });
51
+ await client.start({
52
+ phoneNumber: async function () { await input.text("Please enter your number: "); },
53
+ password: async function () { await input.text("Please enter your password: "); },
54
+ phoneCode: async function () { await input.text("Please enter the code you received: "); },
55
+ onError: function (err) { console.log('Telegram error', err); },
56
+ });
57
+ obj.telegramClient = client;
58
+ obj.providers += 1; // Enable Telegram messaging
59
+ console.log("MeshCentral Telegram client is connected.");
60
+ }
61
+ setupTelegram();
62
+ }
63
+ }
64
+
65
+ // Send an user message
66
+ obj.sendMessage = function(to, msg, func) {
67
+ // Telegram
68
+ if ((to.startsWith('telegram:')) && (obj.telegramClient != null)) {
69
+ async function sendTelegramMessage(to, msg, func) {
70
+ if (obj.telegramClient == null) return;
71
+ parent.debug('email', 'Sending Telegram message to: ' + to.substring(9) + ': ' + msg);
72
+ try { await obj.telegramClient.sendMessage(to.substring(9), { message: msg }); func(true); } catch (ex) { func(false, ex); }
73
+ }
74
+ sendTelegramMessage(to, msg, func);
75
+ } else {
76
+ // No providers found
77
+ func(false, "No messaging providers found for this message.");
78
+ }
79
+ }
80
+
81
+ // Get the correct SMS template
82
+ function getTemplate(templateNumber, domain, lang) {
83
+ parent.debug('email', 'Getting SMS template #' + templateNumber + ', lang: ' + lang);
84
+ if (Array.isArray(lang)) { lang = lang[0]; } // TODO: For now, we only use the first language given.
85
+
86
+ var r = {}, emailsPath = null;
87
+ if ((domain != null) && (domain.webemailspath != null)) { emailsPath = domain.webemailspath; }
88
+ else if (obj.parent.webEmailsOverridePath != null) { emailsPath = obj.parent.webEmailsOverridePath; }
89
+ else if (obj.parent.webEmailsPath != null) { emailsPath = obj.parent.webEmailsPath; }
90
+ if ((emailsPath == null) || (obj.parent.fs.existsSync(emailsPath) == false)) { return null }
91
+
92
+ // Get the non-english email if needed
93
+ var txtfile = null;
94
+ if ((lang != null) && (lang != 'en')) {
95
+ var translationsPath = obj.parent.path.join(emailsPath, 'translations');
96
+ var translationsPathTxt = obj.parent.path.join(emailsPath, 'translations', 'sms-messages_' + lang + '.txt');
97
+ if (obj.parent.fs.existsSync(translationsPath) && obj.parent.fs.existsSync(translationsPathTxt)) {
98
+ txtfile = obj.parent.fs.readFileSync(translationsPathTxt).toString();
99
+ }
100
+ }
101
+
102
+ // Get the english email
103
+ if (txtfile == null) {
104
+ var pathTxt = obj.parent.path.join(emailsPath, 'sms-messages.txt');
105
+ if (obj.parent.fs.existsSync(pathTxt)) {
106
+ txtfile = obj.parent.fs.readFileSync(pathTxt).toString();
107
+ }
108
+ }
109
+
110
+ // No email templates
111
+ if (txtfile == null) { return null; }
112
+
113
+ // Decode the TXT file
114
+ var lines = txtfile.split('\r\n').join('\n').split('\n')
115
+ if (lines.length <= templateNumber) return null;
116
+
117
+ return lines[templateNumber];
118
+ }
119
+
120
+ // Send phone number verification SMS
121
+ obj.sendPhoneCheck = function (domain, to, verificationCode, language, func) {
122
+ parent.debug('email', "Sending verification message to " + to);
123
+
124
+ var sms = getTemplate(0, domain, language);
125
+ if (sms == null) { parent.debug('email', "Error: Failed to get SMS template"); return; } // No SMS template found
126
+
127
+ // Setup the template
128
+ sms = sms.split('[[0]]').join(domain.title ? domain.title : 'MeshCentral');
129
+ sms = sms.split('[[1]]').join(verificationCode);
130
+
131
+ // Send the SMS
132
+ obj.sendSMS(to, sms, func);
133
+ };
134
+
135
+ // Send phone number verification SMS
136
+ obj.sendToken = function (domain, to, verificationCode, language, func) {
137
+ parent.debug('email', "Sending login token message to " + to);
138
+
139
+ var sms = getTemplate(1, domain, language);
140
+ if (sms == null) { parent.debug('email', "Error: Failed to get SMS template"); return; } // No SMS template found
141
+
142
+ // Setup the template
143
+ sms = sms.split('[[0]]').join(domain.title ? domain.title : 'MeshCentral');
144
+ sms = sms.split('[[1]]').join(verificationCode);
145
+
146
+ // Send the SMS
147
+ obj.sendSMS(to, sms, func);
148
+ };
149
+
150
+ return obj;
151
+};
meshsms.js
+7
-1
@@ -37,9 +37,15 @@
37
"apikey": "xxxxxxx",
38
"from": "15555555555"
39
}
40
+
41
+// For URL, add this in config.json
42
+"sms": {
43
+ "provider": "url",
44
+ "url": "https://sample.com/?phone={{phone}}&msg={{message}}"
45
+}
46
*/
47
42
-// Construct a MeshAgent object, called upon connection
48
+// Construct a SMS server object
49
module.exports.CreateMeshSMS = function (parent) {
50
var obj = {};
51
obj.parent = parent;
meshuser.js
+24
-1
@@ -575,6 +575,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
575
if (domain.passwordrequirements.lock2factor == true) { serverinfo.lock2factor = true; } // Indicate 2FA change are not allowed
576
if (typeof domain.passwordrequirements.maxfidokeys == 'number') { serverinfo.maxfidokeys = domain.passwordrequirements.maxfidokeys; }
577
}
578
+ if (parent.parent.msgserver != null) { serverinfo.userMsgProviders = parent.parent.msgserver.providers; }
579
580
// Build the mobile agent URL, this is used to connect mobile devices
581
var agentServerName = parent.getWebServerName(domain, req);
@@ -5315,7 +5316,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
5316
'serverupdate': [serverUserCommandServerUpdate, "Updates server to latest version. Optional version argument to install specific version. Example: serverupdate 0.8.49"],
5317
'setmaxtasks': [serverUserCommandSetMaxTasks, ""],
5318
'showpaths': [serverUserCommandShowPaths, ""],
5318
- 'sms': [serverUserCommandSMS, ""],
5319
+ 'sms': [serverUserCommandSMS, "Send a SMS message to a specified phone number"],
5320
+ 'msg': [serverUserCommandMsg, "Send a user message to a user handle"],
5321
'swarmstats': [serverUserCommandSwarmStats, ""],
5322
'tasklimiter': [serverUserCommandTaskLimiter, "Returns the internal status of the tasklimiter. This is a system used to smooth out work done by the server. It's used by, for example, agent updates so that not all agents are updated at the same time."],
5323
'trafficdelta': [serverUserCommandTrafficDelta, ""],
@@ -6727,6 +6729,27 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
6729
}
6730
}
6731
6732
+ function serverUserCommandMsg(cmdData) {
6733
+ if ((parent.parent.msgserver == null) || (parent.parent.msgserver.providers == 0)) {
6734
+ cmdData.result = "No messaging providers configured.";
6735
+ } else {
6736
+ if (cmdData.cmdargs['_'].length != 2) {
6737
+ var r = [];
6738
+ if ((parent.parent.msgserver.providers & 1) != 0) { r.push("Usage: MSG \"telegram:@UserHandle\" \"Message\"."); }
6739
+ if ((parent.parent.msgserver.providers & 2) != 0) { r.push("Usage: MSG \"signal:@UserHandle\" \"Message\"."); }
6740
+ cmdData.result = r.join('\r\n');
6741
+ } else {
6742
+ parent.parent.msgserver.sendMessage(cmdData.cmdargs['_'][0], cmdData.cmdargs['_'][1], function (status, msg) {
6743
+ if (typeof msg == 'string') {
6744
+ try { ws.send(JSON.stringify({ action: 'serverconsole', value: status ? ('Success: ' + msg) : ('Failed: ' + msg), tag: cmdData.command.tag })); } catch (ex) { }
6745
+ } else {
6746
+ try { ws.send(JSON.stringify({ action: 'serverconsole', value: status ? 'Success' : 'Failed', tag: cmdData.command.tag })); } catch (ex) { }
6747
+ }
6748
+ });
6749
+ }
6750
+ }
6751
+ }
6752
+
6753
function serverUserCommandEmail(cmdData) {
6754
if (domain.mailserver == null) {
6755
cmdData.result = "No email service enabled.";
sample-config-advanced.json
+7
@@ -614,5 +614,12 @@
614
"____sms": {
615
"provider": "url",
616
"url": "http://example.com/sms.ashx?phone={{phone}}&message={{message}}"
617
+ },
618
+ "_messaging": {
619
+ "telegram": {
620
+ "apiid": 0,
621
+ "apihash": "hexBalue",
622
+ "session": "base64Value"
623
+ }
624
}
625
}
telegram.js
new
+136
@@ -0,0 +1,136 @@
1
+/**
2
+* @description MeshCentral Telegram communication module
3
+* @author Ylian Saint-Hilaire
4
+* @copyright Intel Corporation 2018-2022
5
+* @license Apache-2.0
6
+* @version v0.0.1
7
+*/
8
+
9
+/*xjslint node: true */
10
+/*xjslint plusplus: true */
11
+/*xjslint maxlen: 256 */
12
+/*jshint node: true */
13
+/*jshint strict: false */
14
+/*jshint esversion: 6 */
15
+"use strict";
16
+
17
+/*
18
+ "telegram": {
19
+ "apiid": 0000000,
20
+ "apihash": "hexvalue",
21
+ "session": "base64value"
22
+ }
23
+*/
24
+
25
+// Construct a Telegram server object
26
+module.exports.CreateServer = function (parent) {
27
+ var obj = {};
28
+ obj.parent = parent;
29
+
30
+ // Check that we have the correct values
31
+ if (typeof parent.config.telegram != 'object') return null;
32
+ if (typeof parent.config.telegram.apiid != 'number') return null;
33
+ if (typeof parent.config.telegram.apihash != 'string') return null;
34
+ if (typeof parent.config.telegram.session != 'string') return null;
35
+
36
+ // Connect to the telegram server
37
+ async function connect() {
38
+ const { TelegramClient } = require('telegram');
39
+ const { StringSession } = require('telegram/sessions');
40
+ const input = require('input');
41
+
42
+ const stringSession = new StringSession(parent.config.telegram.session);
43
+ const client = new TelegramClient(stringSession, parent.config.telegram.apiid, parent.config.telegram.apihash, { connectionRetries: 5 });
44
+ await client.start({
45
+ phoneNumber: async function () { await input.text("Please enter your number: "); },
46
+ password: async function () { await input.text("Please enter your password: "); },
47
+ phoneCode: async function () { await input.text("Please enter the code you received: "); },
48
+ onError: function (err) { console.log('Telegram error', err); },
49
+ });
50
+ console.log("MeshCentral Telegram session is connected.");
51
+ obj.client = client;
52
+ //console.log(client.session.save()); // Save this string to avoid logging in again
53
+ }
54
+
55
+ // Send an Telegram message
56
+ obj.sendMessage = async function (to, msg, func) {
57
+ if (obj.client == null) return;
58
+ parent.debug('email', 'Sending Telegram to: ' + to + ': ' + msg);
59
+ await client.sendMessage(to, { message: msg });
60
+ func(true);
61
+ }
62
+
63
+ // Get the correct SMS template
64
+ function getTemplate(templateNumber, domain, lang) {
65
+ parent.debug('email', 'Getting SMS template #' + templateNumber + ', lang: ' + lang);
66
+ if (Array.isArray(lang)) { lang = lang[0]; } // TODO: For now, we only use the first language given.
67
+
68
+ var r = {}, emailsPath = null;
69
+ if ((domain != null) && (domain.webemailspath != null)) { emailsPath = domain.webemailspath; }
70
+ else if (obj.parent.webEmailsOverridePath != null) { emailsPath = obj.parent.webEmailsOverridePath; }
71
+ else if (obj.parent.webEmailsPath != null) { emailsPath = obj.parent.webEmailsPath; }
72
+ if ((emailsPath == null) || (obj.parent.fs.existsSync(emailsPath) == false)) { return null }
73
+
74
+ // Get the non-english email if needed
75
+ var txtfile = null;
76
+ if ((lang != null) && (lang != 'en')) {
77
+ var translationsPath = obj.parent.path.join(emailsPath, 'translations');
78
+ var translationsPathTxt = obj.parent.path.join(emailsPath, 'translations', 'sms-messages_' + lang + '.txt');
79
+ if (obj.parent.fs.existsSync(translationsPath) && obj.parent.fs.existsSync(translationsPathTxt)) {
80
+ txtfile = obj.parent.fs.readFileSync(translationsPathTxt).toString();
81
+ }
82
+ }
83
+
84
+ // Get the english email
85
+ if (txtfile == null) {
86
+ var pathTxt = obj.parent.path.join(emailsPath, 'sms-messages.txt');
87
+ if (obj.parent.fs.existsSync(pathTxt)) {
88
+ txtfile = obj.parent.fs.readFileSync(pathTxt).toString();
89
+ }
90
+ }
91
+
92
+ // No email templates
93
+ if (txtfile == null) { return null; }
94
+
95
+ // Decode the TXT file
96
+ var lines = txtfile.split('\r\n').join('\n').split('\n')
97
+ if (lines.length <= templateNumber) return null;
98
+
99
+ return lines[templateNumber];
100
+ }
101
+
102
+ // Send telegram user verification message
103
+ obj.sendPhoneCheck = function (domain, to, verificationCode, language, func) {
104
+ parent.debug('email', "Sending verification Telegram to " + to);
105
+
106
+ var sms = getTemplate(0, domain, language);
107
+ if (sms == null) { parent.debug('email', "Error: Failed to get SMS template"); return; } // No SMS template found
108
+
109
+ // Setup the template
110
+ sms = sms.split('[[0]]').join(domain.title ? domain.title : 'MeshCentral');
111
+ sms = sms.split('[[1]]').join(verificationCode);
112
+
113
+ // Send the SMS
114
+ obj.sendMessage(to, sms, func);
115
+ };
116
+
117
+ // Send login token verification message
118
+ obj.sendToken = function (domain, to, verificationCode, language, func) {
119
+ parent.debug('email', "Sending login token Telegram to " + to);
120
+
121
+ var sms = getTemplate(1, domain, language);
122
+ if (sms == null) { parent.debug('email', "Error: Failed to get SMS template"); return; } // No SMS template found
123
+
124
+ // Setup the template
125
+ sms = sms.split('[[0]]').join(domain.title ? domain.title : 'MeshCentral');
126
+ sms = sms.split('[[1]]').join(verificationCode);
127
+
128
+ // Send the SMS
129
+ obj.sendMessage(to, sms, func);
130
+ };
131
+
132
+ // Connect the Telegram session
133
+ connect();
134
+
135
+ return obj;
136
+};
webserver.js
+2
@@ -3162,6 +3162,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
3162
if (domain.allowsavingdevicecredentials == false) { features2 += 0x00400000; } // Do not allow device credentials to be saved on the server
3163
if ((typeof domain.files == 'object') && (domain.files.sftpconnect === false)) { features2 += 0x00800000; } // Remove the "SFTP Connect" button in the "Files" tab when the device is agent managed
3164
if ((typeof domain.terminal == 'object') && (domain.terminal.sshconnect === false)) { features2 += 0x01000000; } // Remove the "SSH Connect" button in the "Terminal" tab when the device is agent managed
3165
+ if ((parent.msgserver != null) && (parent.msgserver.providers != 0)) { features2 += 0x02000000; } // User messaging server is enabled
3166
+ if ((parent.msgserver != null) && (parent.msgserver.providers != 0) && ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.msg2factor != false))) { features2 += 0x04000000; } // User messaging 2FA is allowed
3167
return { features: features, features2: features2 };
3168
}
3169