Skip to content

Commit 9ce78dc

Browse files
fix(monitor): DNS monitor hostname and other monitors URL validations (#6577)
2 parents a0a009f + f93c302 commit 9ce78dc

File tree

5 files changed

+72
-22
lines changed

5 files changed

+72
-22
lines changed

package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,8 @@
122122
"net-snmp": "^3.11.2",
123123
"node-cloudflared-tunnel": "~1.0.9",
124124
"node-fetch-cache": "^5.1.0",
125-
"nodemailer": "~7.0.12",
126125
"node-radius-utils": "~1.2.0",
126+
"nodemailer": "~7.0.12",
127127
"nostr-tools": "^2.10.4",
128128
"notp": "~2.0.3",
129129
"openid-client": "^5.4.2",
@@ -149,6 +149,7 @@
149149
"thirty-two": "~1.0.2",
150150
"tldts": "^7.0.19",
151151
"tough-cookie": "~4.1.3",
152+
"validator": "^13.15.26",
152153
"web-push": "^3.6.7",
153154
"ws": "^8.13.0"
154155
},

src/lang/en.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,11 @@
276276
"mqttWebsocketPathExplanation": "WebSocket path for MQTT over WebSocket connections (e.g., /mqtt)",
277277
"mqttWebsocketPathInvalid": "Please use a valid WebSocket Path format",
278278
"mqttHostnameTip": "Please use this format {hostnameFormat}",
279+
"hostnameCannotBeIP": "DNS hostname cannot be an IP. Did you mean to use the resolver field?",
280+
"invalidHostnameOrIP": "Invalid hostname or IP. Hostname must be a valid FQDN. Cannot use wildcard. Can have underscore, or end with a dot.",
281+
"invalidDNSHostname": "Invalid hostname. Hostname must be a valid FQDN. Can be a wildcard, have underscore or end with a dot.",
282+
"wildcardOnlyForDNS": "Wildcard hostnames are only supported for DNS monitors.",
283+
"invalidURL": "Invalid URL",
279284
"successKeyword": "Success Keyword",
280285
"successKeywordExplanation": "MQTT Keyword that will be considered as success",
281286
"recent": "Recent",

src/pages/EditMonitor.vue

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,6 @@
326326
v-model="monitor.hostname"
327327
type="text"
328328
class="form-control"
329-
:pattern="`${monitor.type === 'mqtt' ? mqttIpOrHostnameRegexPattern : ipOrHostnameRegexPattern}`"
330329
required
331330
data-testid="hostname-input"
332331
>
@@ -1329,7 +1328,9 @@ import {
13291328
MIN_INTERVAL_SECOND,
13301329
sleep,
13311330
} from "../util.ts";
1332-
import { hostNameRegexPattern, timeDurationFormatter } from "../util-frontend";
1331+
import { timeDurationFormatter } from "../util-frontend";
1332+
import isFQDN from "validator/lib/isFQDN";
1333+
import isIP from "validator/lib/isIP";
13331334
import HiddenInput from "../components/HiddenInput.vue";
13341335
import EditMonitorConditions from "../components/EditMonitorConditions.vue";
13351336
@@ -1417,8 +1418,6 @@ export default {
14171418
acceptedWebsocketCodeOptions: [],
14181419
dnsresolvetypeOptions: [],
14191420
kafkaSaslMechanismOptions: [],
1420-
ipOrHostnameRegexPattern: hostNameRegexPattern(),
1421-
mqttIpOrHostnameRegexPattern: hostNameRegexPattern(true),
14221421
gameList: null,
14231422
connectionStringTemplates: {
14241423
"sqlserver": "Server=<hostname>,<port>;Database=<your database>;User Id=<your user id>;Password=<your password>;Encrypt=<true/false>;TrustServerCertificate=<Yes/No>;Connection Timeout=<int>",
@@ -2083,6 +2082,58 @@ message HealthCheckResponse {
20832082
}
20842083
}
20852084
2085+
// Validate hostname field input for various monitors
2086+
if ([ "mqtt", "dns", "port", "ping", "steam", "gamedig", "radius", "tailscale-ping", "smtp", "snmp" ].includes(this.monitor.type) && this.monitor.hostname) {
2087+
let hostname = this.monitor.hostname.trim();
2088+
2089+
if (this.monitor.type === "mqtt") {
2090+
hostname = hostname.replace(/^(mqtt|ws)s?:\/\//, "");
2091+
}
2092+
2093+
if (this.monitor.type === "dns" && isIP(hostname)) {
2094+
toast.error(this.$t("hostnameCannotBeIP"));
2095+
return false;
2096+
}
2097+
2098+
// Wildcard is allowed only for DNS
2099+
if (!isFQDN(hostname, {
2100+
allow_wildcard: this.monitor.type === "dns",
2101+
require_tld: false,
2102+
allow_underscores: true,
2103+
allow_trailing_dot: true,
2104+
}) && !isIP(hostname)) {
2105+
if (this.monitor.type === "dns") {
2106+
toast.error(this.$t("invalidDNSHostname"));
2107+
} else {
2108+
toast.error(this.$t("invalidHostnameOrIP"));
2109+
}
2110+
return false;
2111+
}
2112+
}
2113+
2114+
// Validate URL field input for various monitors
2115+
if ([ "http", "keyword", "json-query", "websocket-upgrade", "real-browser" ].includes(this.monitor.type) && this.monitor.url) {
2116+
try {
2117+
const url = new URL(this.monitor.url);
2118+
// Browser can encode *.hostname.com to %2A.hostname.com
2119+
if (url.hostname.includes("*") || url.hostname.includes("%2A")) {
2120+
toast.error(this.$t("wildcardOnlyForDNS"));
2121+
return false;
2122+
}
2123+
if (!isFQDN(url.hostname, {
2124+
require_tld: false,
2125+
allow_underscores: true,
2126+
allow_trailing_dot: true,
2127+
}) && !isIP(url.hostname)) {
2128+
toast.error(this.$t("invalidHostnameOrIP"));
2129+
return false;
2130+
}
2131+
} catch (err) {
2132+
toast.error(this.$t("invalidURL"));
2133+
return false;
2134+
}
2135+
}
2136+
20862137
return true;
20872138
},
20882139

src/util-frontend.js

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -108,23 +108,6 @@ export function getDevContainerServerHostname() {
108108
return CODESPACE_NAME + "-3001." + GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN;
109109
}
110110

111-
/**
112-
* Regex pattern fr identifying hostnames and IP addresses
113-
* @param {boolean} mqtt whether or not the regex should take into
114-
* account the fact that it is an mqtt uri
115-
* @returns {RegExp} The requested regex
116-
*/
117-
export function hostNameRegexPattern(mqtt = false) {
118-
// mqtt, mqtts, ws and wss schemes accepted by mqtt.js (https://github.com/mqttjs/MQTT.js/#connect)
119-
const mqttSchemeRegexPattern = "((mqtt|ws)s?:\\/\\/)?";
120-
// Source: https://digitalfortress.tech/tips/top-15-commonly-used-regex/
121-
const ipRegexPattern = `((^${mqtt ? mqttSchemeRegexPattern : ""}((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))$)|(^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?$))`;
122-
// Source: https://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
123-
const hostNameRegexPattern = `^${mqtt ? mqttSchemeRegexPattern : ""}([a-zA-Z0-9])?(([a-zA-Z0-9_]|[a-zA-Z0-9_][a-zA-Z0-9\\-_]*[a-zA-Z0-9_])\\.)*([A-Za-z0-9_]|[A-Za-z0-9_][A-Za-z0-9\\-_]*[A-Za-z0-9_])(\\.)?$`;
124-
125-
return `${ipRegexPattern}|${hostNameRegexPattern}`;
126-
}
127-
128111
/**
129112
* Get the tag color options
130113
* Shared between components

0 commit comments

Comments
 (0)