mirror of
https://github.com/louislam/uptime-kuma.git
synced 2026-03-03 03:07:02 +00:00
fix: Proper processing of date fields (Domain Expiry) with cleanup of unnecessary Date comparison functions (#6638)
Co-authored-by: Frank Elsinga <frank@elsinga.de> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -2,10 +2,11 @@ const { BeanModel } = require("redbean-node/dist/bean-model");
|
|||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { log, TYPES_WITH_DOMAIN_EXPIRY_SUPPORT_VIA_FIELD } = require("../../src/util");
|
const { log, TYPES_WITH_DOMAIN_EXPIRY_SUPPORT_VIA_FIELD } = require("../../src/util");
|
||||||
const { parse: parseTld } = require("tldts");
|
const { parse: parseTld } = require("tldts");
|
||||||
const { getDaysRemaining, getDaysBetween, setting, setSetting } = require("../util-server");
|
const { setting, setSetting } = require("../util-server");
|
||||||
const { Notification } = require("../notification");
|
const { Notification } = require("../notification");
|
||||||
const { default: NodeFetchCache, MemoryCache } = require("node-fetch-cache");
|
const { default: NodeFetchCache, MemoryCache } = require("node-fetch-cache");
|
||||||
const TranslatableError = require("../translatable-error");
|
const TranslatableError = require("../translatable-error");
|
||||||
|
const dayjs = require("dayjs");
|
||||||
|
|
||||||
const cachedFetch = process.env.NODE_ENV
|
const cachedFetch = process.env.NODE_ENV
|
||||||
? NodeFetchCache.create({
|
? NodeFetchCache.create({
|
||||||
@@ -199,18 +200,18 @@ class DomainExpiry extends BeanModel {
|
|||||||
* @returns {Promise<DomainExpiry>} Domain expiry bean
|
* @returns {Promise<DomainExpiry>} Domain expiry bean
|
||||||
*/
|
*/
|
||||||
static async findByDomainNameOrCreate(domainName) {
|
static async findByDomainNameOrCreate(domainName) {
|
||||||
const existing = await DomainExpiry.findByName(domainName);
|
let domain = await DomainExpiry.findByName(domainName);
|
||||||
if (existing) {
|
if (!domain && domainName) {
|
||||||
return existing;
|
domain = await DomainExpiry.createByName(domainName);
|
||||||
}
|
}
|
||||||
return DomainExpiry.createByName(domainName);
|
return domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {number} number of days remaining before expiry
|
* @returns {number} number of days remaining before expiry
|
||||||
*/
|
*/
|
||||||
get daysRemaining() {
|
get daysRemaining() {
|
||||||
return getDaysRemaining(new Date(), new Date(this.expiry));
|
return dayjs.utc(this.expiry).diff(dayjs.utc(), "day");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -227,20 +228,20 @@ class DomainExpiry extends BeanModel {
|
|||||||
*/
|
*/
|
||||||
static async checkExpiry(domainName) {
|
static async checkExpiry(domainName) {
|
||||||
let bean = await DomainExpiry.findByDomainNameOrCreate(domainName);
|
let bean = await DomainExpiry.findByDomainNameOrCreate(domainName);
|
||||||
|
|
||||||
let expiryDate;
|
let expiryDate;
|
||||||
if (bean?.lastCheck && getDaysBetween(new Date(bean.lastCheck), new Date()) < 1) {
|
|
||||||
|
if (bean?.lastCheck && dayjs.utc(bean.lastCheck).diff(dayjs.utc(), "day") < 1) {
|
||||||
log.debug("domain_expiry", `Domain expiry already checked recently for ${bean.domain}, won't re-check.`);
|
log.debug("domain_expiry", `Domain expiry already checked recently for ${bean.domain}, won't re-check.`);
|
||||||
return bean.expiry;
|
return bean.expiry;
|
||||||
} else if (bean) {
|
} else if (bean) {
|
||||||
expiryDate = await bean.getExpiryDate();
|
expiryDate = await bean.getExpiryDate();
|
||||||
|
|
||||||
if (new Date(expiryDate) > new Date(bean.expiry)) {
|
if (dayjs.utc(expiryDate).isAfter(dayjs.utc(bean.expiry))) {
|
||||||
bean.lastExpiryNotificationSent = null;
|
bean.lastExpiryNotificationSent = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
bean.expiry = expiryDate;
|
bean.expiry = R.isoDateTimeMillis(expiryDate);
|
||||||
bean.lastCheck = new Date();
|
bean.lastCheck = R.isoDateTimeMillis(dayjs.utc());
|
||||||
await R.store(bean);
|
await R.store(bean);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,7 +273,7 @@ class DomainExpiry extends BeanModel {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const daysRemaining = getDaysRemaining(new Date(), domain.expiry);
|
const daysRemaining = domain.daysRemaining;
|
||||||
const lastSent = domain.lastExpiryNotificationSent;
|
const lastSent = domain.lastExpiryNotificationSent;
|
||||||
log.debug("domain_expiry", `${domainName} expires in ${daysRemaining} days`);
|
log.debug("domain_expiry", `${domainName} expires in ${daysRemaining} days`);
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ const {
|
|||||||
},
|
},
|
||||||
} = require("node-radius-utils");
|
} = require("node-radius-utils");
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
|
dayjs.extend(require("dayjs/plugin/utc"));
|
||||||
|
|
||||||
// SASLOptions used in JSDoc
|
// SASLOptions used in JSDoc
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
@@ -393,33 +394,6 @@ exports.setSettings = async function (type, data) {
|
|||||||
await Settings.setSettings(type, data);
|
await Settings.setSettings(type, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ssl-checker by @dyaa
|
|
||||||
//https://github.com/dyaa/ssl-checker/blob/master/src/index.ts
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get number of days between two dates
|
|
||||||
* @param {Date} validFrom Start date
|
|
||||||
* @param {Date} validTo End date
|
|
||||||
* @returns {number} Number of days
|
|
||||||
*/
|
|
||||||
const getDaysBetween = (validFrom, validTo) => Math.round(Math.abs(+validFrom - +validTo) / 8.64e7);
|
|
||||||
exports.getDaysBetween = getDaysBetween;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get days remaining from a time range
|
|
||||||
* @param {Date} validFrom Start date
|
|
||||||
* @param {Date} validTo End date
|
|
||||||
* @returns {number} Number of days remaining
|
|
||||||
*/
|
|
||||||
const getDaysRemaining = (validFrom, validTo) => {
|
|
||||||
const daysRemaining = getDaysBetween(validFrom, validTo);
|
|
||||||
if (new Date(validTo).getTime() < new Date(validFrom).getTime()) {
|
|
||||||
return -daysRemaining;
|
|
||||||
}
|
|
||||||
return daysRemaining;
|
|
||||||
};
|
|
||||||
exports.getDaysRemaining = getDaysRemaining;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fix certificate info for display
|
* Fix certificate info for display
|
||||||
* @param {object} info The chain obtained from getPeerCertificate()
|
* @param {object} info The chain obtained from getPeerCertificate()
|
||||||
@@ -440,7 +414,7 @@ const parseCertificateInfo = function (info) {
|
|||||||
}
|
}
|
||||||
link.validTo = new Date(link.valid_to);
|
link.validTo = new Date(link.valid_to);
|
||||||
link.validFor = link.subjectaltname?.replace(/DNS:|IP Address:/g, "").split(", ");
|
link.validFor = link.subjectaltname?.replace(/DNS:|IP Address:/g, "").split(", ");
|
||||||
link.daysRemaining = getDaysRemaining(new Date(), link.validTo);
|
link.daysRemaining = dayjs.utc(link.validTo).diff(dayjs.utc(), "day");
|
||||||
|
|
||||||
existingList[link.fingerprint] = true;
|
existingList[link.fingerprint] = true;
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ const { R } = require("redbean-node");
|
|||||||
const { Notification } = require("../../server/notification");
|
const { Notification } = require("../../server/notification");
|
||||||
const { Settings } = require("../../server/settings");
|
const { Settings } = require("../../server/settings");
|
||||||
const { setSetting } = require("../../server/util-server");
|
const { setSetting } = require("../../server/util-server");
|
||||||
|
const dayjs = require("dayjs");
|
||||||
|
dayjs.extend(require("dayjs/plugin/utc"));
|
||||||
|
|
||||||
const testDb = new TestDB();
|
const testDb = new TestDB();
|
||||||
|
|
||||||
@@ -231,7 +233,7 @@ describe("Domain Expiry", () => {
|
|||||||
test("checkExpiry() caches expiration date in database", async () => {
|
test("checkExpiry() caches expiration date in database", async () => {
|
||||||
await DomainExpiry.checkExpiry("google.com"); // RDAP -> Cache
|
await DomainExpiry.checkExpiry("google.com"); // RDAP -> Cache
|
||||||
const domain = await DomainExpiry.findByName("google.com");
|
const domain = await DomainExpiry.findByName("google.com");
|
||||||
assert(Date.now() - domain.lastCheck < 5 * 1000);
|
assert(dayjs.utc().diff(dayjs.utc(domain.lastCheck), "second") < 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("sendNotifications() triggers notification for expiring domain", async () => {
|
test("sendNotifications() triggers notification for expiring domain", async () => {
|
||||||
@@ -240,7 +242,8 @@ describe("Domain Expiry", () => {
|
|||||||
port: 3010,
|
port: 3010,
|
||||||
url: "capture",
|
url: "capture",
|
||||||
};
|
};
|
||||||
await setSetting("domainExpiryNotifyDays", [1, 2, 1500], "general");
|
const manyDays = 3650;
|
||||||
|
await setSetting("domainExpiryNotifyDays", [manyDays], "general");
|
||||||
const notif = R.convertToBean("notification", {
|
const notif = R.convertToBean("notification", {
|
||||||
config: JSON.stringify({
|
config: JSON.stringify({
|
||||||
type: "webhook",
|
type: "webhook",
|
||||||
@@ -252,8 +255,6 @@ describe("Domain Expiry", () => {
|
|||||||
user_id: 1,
|
user_id: 1,
|
||||||
name: "Testhook",
|
name: "Testhook",
|
||||||
});
|
});
|
||||||
const manyDays = 3650;
|
|
||||||
setSetting("domainExpiryNotifyDays", [manyDays], "general");
|
|
||||||
const [, data] = await Promise.all([
|
const [, data] = await Promise.all([
|
||||||
DomainExpiry.sendNotifications("google.com", [notif]),
|
DomainExpiry.sendNotifications("google.com", [notif]),
|
||||||
mockWebhook(hook.port, hook.url),
|
mockWebhook(hook.port, hook.url),
|
||||||
|
|||||||
@@ -2,33 +2,12 @@ const { describe, test } = require("node:test");
|
|||||||
const assert = require("node:assert");
|
const assert = require("node:assert");
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
|
|
||||||
const { getDaysRemaining, getDaysBetween } = require("../../server/util-server");
|
|
||||||
const { SQL_DATETIME_FORMAT } = require("../../src/util");
|
const { SQL_DATETIME_FORMAT } = require("../../src/util");
|
||||||
|
|
||||||
dayjs.extend(require("dayjs/plugin/utc"));
|
dayjs.extend(require("dayjs/plugin/utc"));
|
||||||
dayjs.extend(require("dayjs/plugin/customParseFormat"));
|
dayjs.extend(require("dayjs/plugin/customParseFormat"));
|
||||||
|
|
||||||
describe("Server Utilities", () => {
|
describe("Server Utilities", () => {
|
||||||
test("getDaysBetween() calculates days between dates within same month", () => {
|
|
||||||
const days = getDaysBetween(new Date(2025, 9, 7), new Date(2025, 9, 10));
|
|
||||||
assert.strictEqual(days, 3);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("getDaysBetween() calculates days between dates across years", () => {
|
|
||||||
const days = getDaysBetween(new Date(2024, 9, 7), new Date(2025, 9, 10));
|
|
||||||
assert.strictEqual(days, 368);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("getDaysRemaining() returns positive value when target date is in future", () => {
|
|
||||||
const days = getDaysRemaining(new Date(2025, 9, 7), new Date(2025, 9, 10));
|
|
||||||
assert.strictEqual(days, 3);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("getDaysRemaining() returns negative value when target date is in past", () => {
|
|
||||||
const days = getDaysRemaining(new Date(2025, 9, 10), new Date(2025, 9, 7));
|
|
||||||
assert.strictEqual(days, -3);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("SQL_DATETIME_FORMAT constant matches MariaDB/MySQL format", () => {
|
test("SQL_DATETIME_FORMAT constant matches MariaDB/MySQL format", () => {
|
||||||
assert.strictEqual(SQL_DATETIME_FORMAT, "YYYY-MM-DD HH:mm:ss");
|
assert.strictEqual(SQL_DATETIME_FORMAT, "YYYY-MM-DD HH:mm:ss");
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user