diff --git a/server/notification-providers/teltonika.js b/server/notification-providers/teltonika.js new file mode 100644 index 000000000..f3d0896d9 --- /dev/null +++ b/server/notification-providers/teltonika.js @@ -0,0 +1,95 @@ +// This notification provider is only compatible with Teltonika RMS >= 7.14.0 devices. +// See: https://community.teltonika.lt/t/implementation-of-read-only-system-files-and-mobile-and-i-o-post-get-service-removal-with-rutos-7-14/12470 +// API reference https://developers.teltonika-networks.com/reference/rut241/7.19.4/v1.11.1/messages + +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); +const https = require("https"); + +class Teltonika extends NotificationProvider { + name = "Teltonika"; + + /** + * @inheritdoc + */ + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + const okMsg = "Sent Successfully."; + + // baseUrl is passed via the configuration screen. + // Must be limited to _just_ the full origin, so: proto://host:port. + // Everything else should be stripped. Best way to validate is to use URL(). + + let passedUrl = ""; + try { + passedUrl = new URL(notification.teltonikaUrl); + } catch (error) { + throw Error("Invalid URL: " + notification.teltonikaUrl); + } + + const baseUrl = passedUrl.origin; + const loginUrl = baseUrl + "/api/login"; + const smsUrl = baseUrl + "/api/messages/actions/send"; + + // Teltonika SMS gateway supports a max of 160 chars for its messages. + const cleanMsg = msg.substring(0, 159); + + // Starting communications with the API from here on out. + try { + let axiosConfig = { + headers: { + "Content-Type": "application/json", + "cache-control": "no-cache", + Accept: "application/json", + }, + }; + + // In many cases, Teltonika routers will be setup using a self-signed + // certificate. Here we give them an option to disable certificate + // validation. It's not desirable, but sometimes the only option. + if (notification.teltonikaUnsafeTls) { + axiosConfig.httpsAgent = new https.Agent({ + rejectUnauthorized: false, // Danger! Disables SSL verification + }); + } + + axiosConfig = this.getAxiosConfigWithProxy(axiosConfig); + + // Logging in, to get an access token. + // API reference https://developers.teltonika-networks.com/reference/rut241/7.19.4/v1.11.1/authentication + // Teltonika's API access tokens expire in 5 minutes, so we always get a new one. + let loginData = { + username: notification.teltonikaUsername, + password: notification.teltonikaPassword, + }; + + let loginResp = await axios.post(loginUrl, loginData, axiosConfig); + + if (loginResp.data.success !== true) { + throw Error("Login failed: " + loginResp.data.errors.error); + } + + // Sending the SMS. + let smsData = { + data: { + modem: notification.teltonikaModem, + number: notification.teltonikaPhoneNumber, + message: cleanMsg, + }, + }; + + axiosConfig.headers.Authorization = "Bearer " + loginResp.data.data.token; + + let smsResp = await axios.post(smsUrl, smsData, axiosConfig); + + if (smsResp.data.success !== true) { + throw Error("Api returned: ", smsResp.data.errors.error); + } + + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error); + } + } +} + +module.exports = Teltonika; diff --git a/server/notification.js b/server/notification.js index afcc2f471..666d0c982 100644 --- a/server/notification.js +++ b/server/notification.js @@ -60,6 +60,7 @@ const Stackfield = require("./notification-providers/stackfield"); const Teams = require("./notification-providers/teams"); const TechulusPush = require("./notification-providers/techulus-push"); const Telegram = require("./notification-providers/telegram"); +const Teltonika = require("./notification-providers/teltonika"); const Threema = require("./notification-providers/threema"); const Twilio = require("./notification-providers/twilio"); const Splunk = require("./notification-providers/splunk"); @@ -165,6 +166,7 @@ class Notification { new Teams(), new TechulusPush(), new Telegram(), + new Teltonika(), new Threema(), new Twilio(), new Splunk(), diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue index 2c95102c0..9dd6f4517 100644 --- a/src/components/NotificationDialog.vue +++ b/src/components/NotificationDialog.vue @@ -272,6 +272,7 @@ export default { SevenIO: "SevenIO", SMSEagle: "SMSEagle", SMSPartner: "SMS Partner", + Teltonika: this.$t("Teltonika SMS Gateway"), twilio: "Twilio", }; diff --git a/src/components/notifications/Teltonika.vue b/src/components/notifications/Teltonika.vue new file mode 100644 index 000000000..a93d0e259 --- /dev/null +++ b/src/components/notifications/Teltonika.vue @@ -0,0 +1,97 @@ + + + diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js index fd21d04f2..8a2ff2043 100644 --- a/src/components/notifications/index.js +++ b/src/components/notifications/index.js @@ -61,6 +61,7 @@ import STMP from "./SMTP.vue"; import Teams from "./Teams.vue"; import TechulusPush from "./TechulusPush.vue"; import Telegram from "./Telegram.vue"; +import Teltonika from "./Teltonika.vue"; import Threema from "./Threema.vue"; import Twilio from "./Twilio.vue"; import Webhook from "./Webhook.vue"; @@ -152,6 +153,7 @@ const NotificationFormList = { stackfield: Stackfield, teams: Teams, telegram: Telegram, + Teltonika: Teltonika, threema: Threema, twilio: Twilio, Splunk: Splunk, diff --git a/src/lang/en.json b/src/lang/en.json index af06934db..764bffb4e 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -1457,5 +1457,19 @@ "halopsa_field_timestamp": "Event timestamp in ISO 8601 format", "halopsa_field_uptime_kuma_version": "Uptime Kuma version number", "halopsa_id_usage_hint": "💡 Tip: Use monitor_id to reliably match alerts to tickets, and heartbeat_id to track event history", - "halopsa_setup_step5": "Configure runbook to use monitor_id for matching alerts to existing tickets" + "halopsa_setup_step5": "Configure runbook to use monitor_id for matching alerts to existing tickets", + "Teltonika SMS Gateway": "Teltonika SMS Gateway", + "teltonikaVersionWarning": "This notification provider requires that your Teltonika device runs RMS version 7.14.0, or higher.", + "teltonikaUrl": "Your Teltonika device URL", + "teltonikaUrlHelptext": "URL should be specified as full origin, e.g. {0}, or {1}.", + "teltonikaUnsafeTls": "Ignore certificate validation", + "teltonikaUnsafeTlsDescription": "Turning off TLS certificate validation opens you up to on-path (man-in-the-middle) attacks, potentially leading to data leaks and systems take-over. Do not turn off certificate validation unless you accept this attack vector. We recomend using LetsEncrypt with automatic renewal.", + "teltonikaUsername": "API username", + "teltonikaUsernameHelptext": "Recommendation: Create a separate account which is restricted to only sending SMS messages and enter its username here", + "teltonikaPassword": "API password", + "teltonikaPasswordHelptext": "You can define the API user's password in your Teltonika router, e.g. {0}", + "teltonikaModem": "Modem Id", + "teltonikaModemHelptext": "The id of the SMS modem, must be in the format {0}. Refer to https://developers.teltonika-networks.com/reference/ for guidance.", + "teltonikaPhoneNumber": "Phone number", + "teltonikaPhoneNumberHelptext": "The number must be in the international format {0}, {1}. Only one number is allowed." }