From 10e127d0dba5393ce0020faa59ae7616f45f4687 Mon Sep 17 00:00:00 2001 From: Malena Aguiar Date: Tue, 22 Sep 2026 16:27:20 -0600 Subject: [PATCH] fix: resolve ssl certificate port issue --- server/src/api/controllers/controllerUtils.ts | 5 +- .../unit/utils/monitorCertificate.test.ts | 68 +++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 server/test/unit/utils/monitorCertificate.test.ts diff --git a/server/src/api/controllers/controllerUtils.ts b/server/src/api/controllers/controllerUtils.ts index 608c39d57..0c3a02bf5 100755 --- a/server/src/api/controllers/controllerUtils.ts +++ b/server/src/api/controllers/controllerUtils.ts @@ -10,7 +10,10 @@ type WhoisModule = typeof whoiser; export const fetchMonitorCertificate = async (checker: SSLCheckerType, monitor: Monitor): Promise => { const monitorUrl = new URL(monitor.url); const hostname = monitorUrl.hostname; - const cert = await checker(hostname); + // An explicit port in the URL is the most visible to the user, so it takes precedence over monitor.port. + const urlPort = monitorUrl.port ? Number(monitorUrl.port) : undefined; + const port = urlPort ?? monitor.port; + const cert = await checker(hostname, port !== undefined ? { port } : undefined); if (cert?.validTo === null || cert?.validTo === undefined) { throw new Error("Certificate not found"); } diff --git a/server/test/unit/utils/monitorCertificate.test.ts b/server/test/unit/utils/monitorCertificate.test.ts new file mode 100644 index 000000000..7fe6a7a52 --- /dev/null +++ b/server/test/unit/utils/monitorCertificate.test.ts @@ -0,0 +1,68 @@ +import { describe, expect, it } from "@jest/globals"; +import type { SSLDetails, SSLOptions } from "ssl-checker"; +import { fetchMonitorCertificate } from "../../../src/api/controllers/controllerUtils.ts"; +import type { Monitor } from "../../../src/domain/monitors/monitor.type.ts"; + +const makeMonitor = (url: string, port?: number): Monitor => + ({ + url, + port, + type: "http", + }) as unknown as Monitor; + +const cert: SSLDetails = { + daysRemaining: 30, + valid: true, + validFrom: "2026-01-01T00:00:00Z", + validTo: "2026-12-31T00:00:00Z", + validFor: ["example.com"], +}; + +const setup = () => { + const calls: { hostname: string; options?: SSLOptions }[] = []; + const checker = async (hostname: string, options?: SSLOptions) => { + calls.push({ hostname, options }); + return cert; + }; + return { calls, checker }; +}; + +describe("fetchMonitorCertificate", () => { + it("uses the default port when none is specified", async () => { + const { calls, checker } = setup(); + + await fetchMonitorCertificate(checker, makeMonitor("https://example.com/health")); + + expect(calls).toEqual([{ hostname: "example.com", options: undefined }]); + }); + + it("uses the port from the URL", async () => { + const { calls, checker } = setup(); + + await fetchMonitorCertificate(checker, makeMonitor("https://example.com:54321/health")); + + expect(calls).toEqual([{ hostname: "example.com", options: { port: 54321 } }]); + }); + + it("falls back to monitor.port when the URL has no port", async () => { + const { calls, checker } = setup(); + + await fetchMonitorCertificate(checker, makeMonitor("https://example.com/health", 8443)); + + expect(calls).toEqual([{ hostname: "example.com", options: { port: 8443 } }]); + }); + + it("prefers the URL port over monitor.port", async () => { + const { calls, checker } = setup(); + + await fetchMonitorCertificate(checker, makeMonitor("https://www.myurl.com:52345/health", 443)); + + expect(calls).toEqual([{ hostname: "www.myurl.com", options: { port: 52345 } }]); + }); + + it("throws when the certificate has no expiry", async () => { + const checker = async () => ({ ...cert, validTo: undefined }) as unknown as SSLDetails; + + await expect(fetchMonitorCertificate(checker, makeMonitor("https://example.com"))).rejects.toThrow("Certificate not found"); + }); +});