diff --git a/db/patch-add-radius-monitor.sql b/db/patch-add-radius-monitor.sql new file mode 100644 index 000000000..1fd5b44f4 --- /dev/null +++ b/db/patch-add-radius-monitor.sql @@ -0,0 +1,18 @@ +BEGIN TRANSACTION; + +ALTER TABLE monitor + ADD radius_username VARCHAR(255); + +ALTER TABLE monitor + ADD radius_password VARCHAR(255); + +ALTER TABLE monitor + ADD radius_calling_station_id VARCHAR(50); + +ALTER TABLE monitor + ADD radius_called_station_id VARCHAR(50); + +ALTER TABLE monitor + ADD radius_secret VARCHAR(255); + +COMMIT diff --git a/package-lock.json b/package-lock.json index 778e6bc38..0cf62fa7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,7 @@ "mqtt": "^4.2.8", "mssql": "^8.1.0", "node-cloudflared-tunnel": "~1.0.9", + "node-radius-client": "^1.0.0", "nodemailer": "~6.6.5", "notp": "~2.0.3", "password-hash": "~1.2.2", @@ -8215,6 +8216,12 @@ "readable-stream": "^3.6.0" } }, + "node_modules/hoek": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", + "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==", + "deprecated": "This module has moved and is now available at @hapi/hoek. Please update your dependencies as this version is no longer maintained an may contain bugs and security issues." + }, "node_modules/homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -8915,6 +8922,17 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "devOptional": true }, + "node_modules/isemail": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", + "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", + "dependencies": { + "punycode": "2.x.x" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -12151,6 +12169,32 @@ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true }, + "node_modules/node-radius-client": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-radius-client/-/node-radius-client-1.0.0.tgz", + "integrity": "sha512-FkR9cMV5hNoX+kKDUTzuagvEixlLiaEJQ1/ywOdhahsihKrGDhVZmnCvmrCStA589MT3yuC/J2eKc6z68IGdBw==", + "dependencies": { + "joi": "^14.3.1", + "node-radius-utils": "^1.2.0", + "radius": "^1.1.4" + } + }, + "node_modules/node-radius-client/node_modules/joi": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-14.3.1.tgz", + "integrity": "sha512-LQDdM+pkOrpAn4Lp+neNIFV3axv1Vna3j38bisbQhETPMANYRbFJFUyOZcOClYvM/hppMhGWuKSFEK9vjrB+bQ==", + "deprecated": "This module has moved and is now available at @hapi/joi. Please update your dependencies as this version is no longer maintained an may contain bugs and security issues.", + "dependencies": { + "hoek": "6.x.x", + "isemail": "3.x.x", + "topo": "3.x.x" + } + }, + "node_modules/node-radius-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/node-radius-utils/-/node-radius-utils-1.2.0.tgz", + "integrity": "sha512-i3Sf6khnenl0aXumo0whAlfPWTaBqHxEnVBBxpu3dZ7q69NkPPv71rvPjlDZ5wkeKCTNNUTECljerS5kcYQxRw==" + }, "node_modules/node-releases": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", @@ -13429,6 +13473,14 @@ "node": ">=8" } }, + "node_modules/radius": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/radius/-/radius-1.1.4.tgz", + "integrity": "sha512-UWuzdF6xf3NpsXFZZmUEkxtEalDXj8hdmMXgbGzn7vOk6zXNsiIY2I6SJ1euHt7PTQuMoz2qDEJB+AfJDJgQYw==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -15261,6 +15313,15 @@ "node": ">=0.6" } }, + "node_modules/topo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz", + "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==", + "deprecated": "This module has moved and is now available at @hapi/topo. Please update your dependencies as this version is no longer maintained an may contain bugs and security issues.", + "dependencies": { + "hoek": "6.x.x" + } + }, "node_modules/toposort": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", @@ -22641,6 +22702,11 @@ "readable-stream": "^3.6.0" } }, + "hoek": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", + "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==" + }, "homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -23123,6 +23189,14 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "devOptional": true }, + "isemail": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", + "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", + "requires": { + "punycode": "2.x.x" + } + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -25618,6 +25692,33 @@ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true }, + "node-radius-client": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-radius-client/-/node-radius-client-1.0.0.tgz", + "integrity": "sha512-FkR9cMV5hNoX+kKDUTzuagvEixlLiaEJQ1/ywOdhahsihKrGDhVZmnCvmrCStA589MT3yuC/J2eKc6z68IGdBw==", + "requires": { + "joi": "^14.3.1", + "node-radius-utils": "^1.2.0", + "radius": "^1.1.4" + }, + "dependencies": { + "joi": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-14.3.1.tgz", + "integrity": "sha512-LQDdM+pkOrpAn4Lp+neNIFV3axv1Vna3j38bisbQhETPMANYRbFJFUyOZcOClYvM/hppMhGWuKSFEK9vjrB+bQ==", + "requires": { + "hoek": "6.x.x", + "isemail": "3.x.x", + "topo": "3.x.x" + } + } + } + }, + "node-radius-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/node-radius-utils/-/node-radius-utils-1.2.0.tgz", + "integrity": "sha512-i3Sf6khnenl0aXumo0whAlfPWTaBqHxEnVBBxpu3dZ7q69NkPPv71rvPjlDZ5wkeKCTNNUTECljerS5kcYQxRw==" + }, "node-releases": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", @@ -26532,6 +26633,11 @@ "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", "dev": true }, + "radius": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/radius/-/radius-1.1.4.tgz", + "integrity": "sha512-UWuzdF6xf3NpsXFZZmUEkxtEalDXj8hdmMXgbGzn7vOk6zXNsiIY2I6SJ1euHt7PTQuMoz2qDEJB+AfJDJgQYw==" + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -27967,6 +28073,14 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, + "topo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz", + "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==", + "requires": { + "hoek": "6.x.x" + } + }, "toposort": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", diff --git a/package.json b/package.json index ea6c5a791..bac451d5e 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "mqtt": "^4.2.8", "mssql": "^8.1.0", "node-cloudflared-tunnel": "~1.0.9", + "node-radius-client": "^1.0.0", "nodemailer": "~6.6.5", "notp": "~2.0.3", "password-hash": "~1.2.2", diff --git a/server/database.js b/server/database.js index ecf6af728..69bb3e5c8 100644 --- a/server/database.js +++ b/server/database.js @@ -62,6 +62,7 @@ class Database { "patch-add-clickable-status-page-link.sql": true, "patch-add-sqlserver-monitor.sql": true, "patch-add-other-auth.sql": { parents: [ "patch-monitor-basic-auth.sql" ] }, + "patch-add-radius-monitor.sql": true, "patch-http-body-encoding.sql": true }; diff --git a/server/model/monitor.js b/server/model/monitor.js index af3d162a5..5d9c4cd42 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -7,7 +7,7 @@ dayjs.extend(timezone); const axios = require("axios"); const { Prometheus } = require("../prometheus"); const { log, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util"); -const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mqttAsync, setSetting, httpNtlm } = require("../util-server"); +const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mqttAsync, setSetting, httpNtlm, radius } = require("../util-server"); const { R } = require("redbean-node"); const { BeanModel } = require("redbean-node/dist/bean-model"); const { Notification } = require("../notification"); @@ -103,6 +103,11 @@ class Monitor extends BeanModel { authMethod: this.authMethod, authWorkstation: this.authWorkstation, authDomain: this.authDomain, + radiusUsername: this.radiusUsername, + radiusPassword: this.radiusPassword, + radiusCalledStationId: this.radiusCalledStationId, + radiusCallingStationId: this.radiusCallingStationId, + radiusSecret: this.radiusSecret, httpBodyEncoding: this.httpBodyEncoding }; @@ -542,6 +547,30 @@ class Monitor extends BeanModel { bean.msg = ""; bean.status = UP; bean.ping = dayjs().valueOf() - startTime; + } else if (this.type === "radius") { + let startTime = dayjs().valueOf(); + try { + const resp = await radius( + this.hostname, + this.radiusUsername, + this.radiusPassword, + this.radiusCalledStationId, + this.radiusCallingStationId, + this.radiusSecret + ); + if (resp.code) { + bean.msg = resp.code; + } + bean.status = UP; + } catch (error) { + bean.status = DOWN; + if (error.response?.code) { + bean.msg = error.response.code; + } else { + bean.msg = error.message; + } + } + bean.ping = dayjs().valueOf() - startTime; } else { bean.msg = "Unknown Monitor Type"; bean.status = PENDING; diff --git a/server/server.js b/server/server.js index 616a10cda..7ad82a24f 100644 --- a/server/server.js +++ b/server/server.js @@ -693,6 +693,11 @@ let needSetup = false; bean.authMethod = monitor.authMethod; bean.authWorkstation = monitor.authWorkstation; bean.authDomain = monitor.authDomain; + bean.radiusUsername = monitor.radiusUsername; + bean.radiusPassword = monitor.radiusPassword; + bean.radiusCalledStationId = monitor.radiusCalledStationId; + bean.radiusCallingStationId = monitor.radiusCallingStationId; + bean.radiusSecret = monitor.radiusSecret; bean.httpBodyEncoding = monitor.httpBodyEncoding; await R.store(bean); diff --git a/server/util-server.js b/server/util-server.js index df711cf05..067da6fd5 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -15,6 +15,12 @@ const { Client } = require("pg"); const postgresConParse = require("pg-connection-string").parse; const { NtlmClient } = require("axios-ntlm"); const { Settings } = require("./settings"); +const radiusClient = require("node-radius-client"); +const { + dictionaries: { + rfc2865: { file, attributes }, + }, +} = require("node-radius-utils"); // From ping-lite exports.WIN = /^win/.test(process.platform); @@ -285,6 +291,30 @@ exports.postgresQuery = function (connectionString, query) { }); }; +exports.radius = function ( + hostname, + username, + password, + calledStationId, + callingStationId, + secret, +) { + const client = new radiusClient({ + host: hostname, + dictionaries: [ file ], + }); + + return client.accessRequest({ + secret: secret, + attributes: [ + [ attributes.USER_NAME, username ], + [ attributes.USER_PASSWORD, password ], + [ attributes.CALLING_STATION_ID, callingStationId ], + [ attributes.CALLED_STATION_ID, calledStationId ], + ], + }); +}; + /** * Retrieve value of setting based on key * @param {string} key Key of setting to retrieve diff --git a/src/languages/en.js b/src/languages/en.js index 4433e2a5f..e338d7785 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -482,6 +482,12 @@ export default { "Domain Names": "Domain Names", signedInDisp: "Signed in as {0}", signedInDispDisabled: "Auth Disabled.", + RadiusSecret: "Radius Secret", + RadiusSecretDescription: "Shared Secret between client and server", + RadiusCalledStationId: "Called Station Id", + RadiusCalledStationIdDescription: "Identifier of the called device", + RadiusCallingStationId: "Calling Station Id", + RadiusCallingStationIdDescription: "Identifier of the calling device", "Certificate Expiry Notification": "Certificate Expiry Notification", "API Username": "API Username", "API Key": "API Key", diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index b4aceba7b..aa3d1a5ff 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -51,6 +51,9 @@ + @@ -87,8 +90,8 @@ - -
+ +
@@ -202,6 +205,36 @@
+ +