fix(domain-expiry): only enable domain expiry notification when TLD has RDAP support (#6884)

This commit is contained in:
Røb
2026-02-07 19:29:47 -05:00
committed by GitHub
parent e4cffb384b
commit 6ef9de497e
3 changed files with 195 additions and 1 deletions

View File

@@ -0,0 +1,85 @@
const { parse: parseTld } = require("tldts");
const rdapDnsData = require("../../server/model/rdap-dns.json");
const TYPES_WITH_DOMAIN_EXPIRY_SUPPORT_VIA_FIELD = {
http: "url",
keyword: "url",
"json-query": "url",
"real-browser": "url",
"websocket-upgrade": "url",
port: "hostname",
ping: "hostname",
"grpc-keyword": "grpc_url",
dns: "hostname",
smtp: "hostname",
snmp: "hostname",
gamedig: "hostname",
steam: "hostname",
mqtt: "hostname",
radius: "hostname",
"tailscale-ping": "hostname",
"sip-options": "hostname",
};
/**
* Build set of root TLDs that have RDAP support
* @returns {Set<string>} Set of supported root TLDs
*/
function getSupportedTlds() {
const supported = new Set();
const services = rdapDnsData["services"] ?? [];
for (const [tlds] of services) {
for (const tld of tlds) {
supported.add(tld);
}
}
return supported;
}
/**
* Check if a target URL/hostname has RDAP support
* @param {string} target URL or hostname
* @param {Set<string>} supportedTlds Set of supported root TLDs
* @returns {boolean} Whether the target's TLD has RDAP support
*/
function hasRdapSupport(target, supportedTlds) {
if (!target || typeof target !== "string") {
return false;
}
const tld = parseTld(target);
if (!tld.publicSuffix || !tld.isIcann) {
return false;
}
const rootTld = tld.publicSuffix.split(".").pop();
return supportedTlds.has(rootTld);
}
exports.up = async function (knex) {
const supportedTlds = getSupportedTlds();
const monitors = await knex("monitor")
.where("domain_expiry_notification", 1)
.select("id", "type", "url", "hostname", "grpc_url");
const idsToDisable = [];
for (const monitor of monitors) {
const targetField = TYPES_WITH_DOMAIN_EXPIRY_SUPPORT_VIA_FIELD[monitor.type];
if (!targetField || !hasRdapSupport(monitor[targetField], supportedTlds)) {
idsToDisable.push(monitor.id);
}
}
if (idsToDisable.length > 0) {
await knex("monitor").whereIn("id", idsToDisable).update("domain_expiry_notification", 0);
}
await knex.schema.alterTable("monitor", function (table) {
table.boolean("domain_expiry_notification").defaultTo(0).alter();
});
};
exports.down = async function (knex) {
await knex.schema.alterTable("monitor", function (table) {
table.boolean("domain_expiry_notification").defaultTo(1).alter();
});
};

View File

@@ -2761,7 +2761,7 @@ const monitorDefaults = {
ignoreTls: false, ignoreTls: false,
upsideDown: false, upsideDown: false,
expiryNotification: false, expiryNotification: false,
domainExpiryNotification: true, domainExpiryNotification: false,
maxredirects: 10, maxredirects: 10,
accepted_statuscodes: ["200-299"], accepted_statuscodes: ["200-299"],
saveResponse: false, saveResponse: false,
@@ -3199,7 +3199,11 @@ message HealthCheckResponse {
this.checkMonitorDebounce = setTimeout(() => { this.checkMonitorDebounce = setTimeout(() => {
this.$root.getSocket().emit("checkMointor", data, (res) => { this.$root.getSocket().emit("checkMointor", data, (res) => {
const wasSupported = this.hasDomain;
this.hasDomain = !!res?.ok; this.hasDomain = !!res?.ok;
if (this.hasDomain !== wasSupported) {
this.monitor.domainExpiryNotification = this.hasDomain;
}
this.domainExpiryUnsupportedReason = res.msgi18n ? this.$t(res.msg, res.meta) : res.msg; this.domainExpiryUnsupportedReason = res.msgi18n ? this.$t(res.msg, res.meta) : res.msg;
}); });
}, 500); }, 500);

View File

@@ -0,0 +1,105 @@
import { expect, test } from "@playwright/test";
import { login, restoreSqliteSnapshot, screenshot } from "../util-test";
test.describe("Domain Expiry Notification", () => {
test.beforeEach(async ({ page }) => {
await restoreSqliteSnapshot(page);
});
test("supported TLD auto-enables checkbox", async ({ page }, testInfo) => {
await page.goto("./add");
await login(page);
const monitorTypeSelect = page.getByTestId("monitor-type-select");
await monitorTypeSelect.selectOption("http");
await page.getByTestId("url-input").fill("https://example.com");
const checkbox = page.getByLabel("Domain Name Expiry Notification");
await expect(checkbox).toBeChecked();
await expect(checkbox).toBeEnabled();
await screenshot(testInfo, page);
});
test("unsupported TLD leaves checkbox disabled", async ({ page }, testInfo) => {
await page.goto("./add");
await login(page);
const monitorTypeSelect = page.getByTestId("monitor-type-select");
await monitorTypeSelect.selectOption("http");
await page.getByTestId("url-input").fill("https://example.co");
const checkbox = page.getByLabel("Domain Name Expiry Notification");
await expect(checkbox).not.toBeChecked();
await expect(checkbox).toBeDisabled();
await screenshot(testInfo, page);
});
test("switching from supported to unsupported TLD disables checkbox", async ({ page }, testInfo) => {
await page.goto("./add");
await login(page);
const monitorTypeSelect = page.getByTestId("monitor-type-select");
await monitorTypeSelect.selectOption("http");
const urlInput = page.getByTestId("url-input");
const checkbox = page.getByLabel("Domain Name Expiry Notification");
await urlInput.fill("https://example.com");
await expect(checkbox).toBeChecked();
await urlInput.fill("https://example.co");
await expect(checkbox).not.toBeChecked();
await expect(checkbox).toBeDisabled();
await screenshot(testInfo, page);
});
test("switching from unsupported to supported TLD enables checkbox", async ({ page }, testInfo) => {
await page.goto("./add");
await login(page);
const monitorTypeSelect = page.getByTestId("monitor-type-select");
await monitorTypeSelect.selectOption("http");
const urlInput = page.getByTestId("url-input");
const checkbox = page.getByLabel("Domain Name Expiry Notification");
await urlInput.fill("https://example.co");
await expect(checkbox).not.toBeChecked();
await urlInput.fill("https://example.com");
await expect(checkbox).toBeChecked();
await expect(checkbox).toBeEnabled();
await screenshot(testInfo, page);
});
test("manual uncheck preserved when URL changes within same TLD", async ({ page }, testInfo) => {
await page.goto("./add");
await login(page);
const monitorTypeSelect = page.getByTestId("monitor-type-select");
await monitorTypeSelect.selectOption("http");
const urlInput = page.getByTestId("url-input");
const checkbox = page.getByLabel("Domain Name Expiry Notification");
await urlInput.fill("https://example.com");
await expect(checkbox).toBeChecked();
await checkbox.uncheck();
await expect(checkbox).not.toBeChecked();
await urlInput.fill("https://example.com/different-path");
// Wait for debounce to fire and verify checkbox stays unchecked
await page.waitForTimeout(600);
await expect(checkbox).not.toBeChecked();
await expect(checkbox).toBeEnabled();
await screenshot(testInfo, page);
});
});