mirror of
https://github.com/louislam/uptime-kuma.git
synced 2026-03-03 03:07:02 +00:00
fix(domain-expiry): only enable domain expiry notification when TLD has RDAP support (#6884)
This commit is contained in:
@@ -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();
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -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);
|
||||||
|
|||||||
105
test/e2e/specs/domain-expiry-notification.spec.js
Normal file
105
test/e2e/specs/domain-expiry-notification.spec.js
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user