From 79cf169ede3a7cb715881b71560d9d2c8bbaaa11 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 17 Oct 2024 11:57:37 +0800 Subject: [PATCH] Add ssl-checker, refactor fetchMonitorCertificate --- Server/controllers/controllerUtils.js | 31 ++---- Server/controllers/monitorController.js | 10 +- Server/package-lock.json | 7 ++ Server/package.json | 7 +- .../tests/controllers/controllerUtils.test.js | 101 ++++-------------- 5 files changed, 42 insertions(+), 114 deletions(-) diff --git a/Server/controllers/controllerUtils.js b/Server/controllers/controllerUtils.js index 313c2a09a..38c45b3cb 100644 --- a/Server/controllers/controllerUtils.js +++ b/Server/controllers/controllerUtils.js @@ -12,32 +12,15 @@ const handleError = (error, serviceName, method, status = 500) => { return error; }; -const fetchMonitorCertificate = async (tls, monitor) => { - return new Promise((resolve, reject) => { +const fetchMonitorCertificate = async (sslChecker, monitor) => { + try { const monitorUrl = new URL(monitor.url); const hostname = monitorUrl.hostname; - try { - let socket = tls.connect( - { - port: 443, - host: hostname, - servername: hostname, // this is required in case the server enabled SNI - }, - () => { - try { - let x509Certificate = socket.getPeerX509Certificate(); - resolve(x509Certificate); - } catch (error) { - reject(error); - } finally { - socket.end(); - } - } - ); - } catch (error) { - reject(error); - } - }); + const cert = await sslChecker(hostname); + return cert; + } catch (error) { + throw error; + } }; export { handleValidationError, handleError, fetchMonitorCertificate }; diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index b675ab652..91c1e3f74 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -12,18 +12,14 @@ import { getMonitorStatsByIdQueryValidation, getCertificateParamValidation, } from "../validation/joi.js"; -import * as tls from "tls"; +import sslChecker from "ssl-checker"; const SERVICE_NAME = "monitorController"; import { errorMessages, successMessages } from "../utils/messages.js"; import jwt from "jsonwebtoken"; import { getTokenFromHeaders } from "../utils/utils.js"; import logger from "../utils/logger.js"; -import { - handleError, - handleValidationError, - fetchMonitorCertificate, -} from "./controllerUtils.js"; +import { handleError, handleValidationError } from "./controllerUtils.js"; /** * Returns all monitors @@ -87,7 +83,7 @@ const getMonitorCertificate = async (req, res, next, fetchMonitorCertificate) => try { const { monitorId } = req.params; const monitor = await req.db.getMonitorById(monitorId); - const certificate = await fetchMonitorCertificate(tls, monitor); + const certificate = await fetchMonitorCertificate(sslChecker, monitor); if (certificate && certificate.validTo) { return res.status(200).json({ success: true, diff --git a/Server/package-lock.json b/Server/package-lock.json index 51f940003..a226d79b8 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -27,6 +27,7 @@ "nodemailer": "^6.9.14", "ping": "0.4.4", "sharp": "0.33.4", + "ssl-checker": "2.0.10", "swagger-ui-express": "5.0.1", "winston": "^3.13.0" }, @@ -6002,6 +6003,12 @@ "memory-pager": "^1.0.2" } }, + "node_modules/ssl-checker": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/ssl-checker/-/ssl-checker-2.0.10.tgz", + "integrity": "sha512-SS6rrZocToJWHM1p6iVNb583ybB3UqT1fymCHSWuEdXDUqKA6O1D5Fb8KJVmhj3XKXE82IEWcr+idJrc4jUzFQ==", + "license": "MIT" + }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", diff --git a/Server/package.json b/Server/package.json index a4835fdee..c46ee3e49 100644 --- a/Server/package.json +++ b/Server/package.json @@ -30,16 +30,17 @@ "nodemailer": "^6.9.14", "ping": "0.4.4", "sharp": "0.33.4", + "ssl-checker": "2.0.10", "swagger-ui-express": "5.0.1", "winston": "^3.13.0" }, "devDependencies": { - "prettier": "^3.3.3", - "chai": "5.1.1", - "mocha": "10.7.3", "c8": "10.1.2", + "chai": "5.1.1", "esm": "3.2.25", + "mocha": "10.7.3", "nodemon": "3.1.0", + "prettier": "^3.3.3", "sinon": "19.0.2" } } diff --git a/Server/tests/controllers/controllerUtils.test.js b/Server/tests/controllers/controllerUtils.test.js index 55c16b857..7f5d9b449 100644 --- a/Server/tests/controllers/controllerUtils.test.js +++ b/Server/tests/controllers/controllerUtils.test.js @@ -6,6 +6,9 @@ import { fetchMonitorCertificate, } from "../../controllers/controllerUtils.js"; import { expect } from "chai"; +import sslChecker from "ssl-checker"; +import { afterEach } from "node:test"; +import exp from "constants"; describe("controllerUtils - handleValidationError", () => { it("should set status to 422", () => { @@ -109,102 +112,40 @@ describe("controllerUtils - handleError", () => { }); describe("controllerUtils - fetchMonitorCertificate", () => { - const originalTls = { - connect: sinon.stub().callsFake((options, callback) => { - // Create socket stub with sinon stubs for all methods - socket = { - getPeerX509Certificate: sinon.stub().returns({ - subject: "CN=fake-cert", - validTo: "Dec 31 23:59:59 2023 GMT", - }), - end: sinon.stub(), - on: sinon.stub(), - }; - - // Use process.nextTick to ensure async behavior - process.nextTick(() => { - callback.call(socket); // Ensure correct 'this' binding - }); - - return socket; - }), - }; - - let tls, monitor, socket; + let sslChecker, monitor; beforeEach(() => { - monitor = { url: "https://www.google.com" }; - tls = { - connect: sinon.stub().callsFake((options, callback) => { - // Create socket stub with sinon stubs for all methods - socket = { - getPeerX509Certificate: sinon.stub().returns({ - subject: "CN=fake-cert", - validTo: "Dec 31 23:59:59 2023 GMT", - }), - end: sinon.stub(), - on: sinon.stub(), - }; - - // Use process.nextTick to ensure async behavior - process.nextTick(() => { - callback.call(socket); // Ensure correct 'this' binding - }); - - return socket; - }), + monitor = { + url: "https://www.google.com", }; + sslChecker = sinon.stub(); }); afterEach(() => { - tls = { ...originalTls }; sinon.restore(); }); - it("should resolve with the certificate when the connection is successful", async () => { - const certificate = await fetchMonitorCertificate(tls, monitor); - expect(certificate.validTo).to.equal("Dec 31 23:59:59 2023 GMT"); - expect(socket.end.calledOnce).to.be.true; - }); - - it("should reject with an error when the connection fails", async () => { - tls.connect = sinon.stub().throws(new Error("Connection error")); + it("should reject with an error if a URL does not parse", async () => { + monitor.url = "invalidurl"; try { - await fetchMonitorCertificate(tls, monitor); - } catch (error) { - expect(error.message).to.equal("Connection error"); - } - }); - - it("should reject with an error if monitorURL is invalid", async () => { - monitor.url = "invalid-url"; - try { - await fetchMonitorCertificate(tls, monitor); + await fetchMonitorCertificate(sslChecker, monitor); } catch (error) { + expect(error).to.be.an("error"); expect(error.message).to.equal("Invalid URL"); } }); - it("should do a thing", async () => { - tls = { - connect: sinon.stub().callsFake((options, callback) => { - // Create socket stub with sinon stubs for all methods - socket = { - getPeerX509Certificate: sinon.stub().throws(new Error("Certificate error")), - end: sinon.stub(), - on: sinon.stub(), - }; - // Use process.nextTick to ensure async behavior - process.nextTick(() => { - callback.call(socket); // Ensure correct 'this' binding - }); - - return socket; - }), - }; + it("should reject with an error if sslChecker throws an error", async () => { + sslChecker.rejects(new Error("Test error")); try { - await fetchMonitorCertificate(tls, monitor); + await fetchMonitorCertificate(sslChecker, monitor); } catch (error) { - expect(error.message).to.equal("Certificate error"); + expect(error).to.be.an("error"); + expect(error.message).to.equal("Test error"); } }); + it("should return a certificate if sslChecker resolves", async () => { + sslChecker.resolves({ validTo: "2022-01-01" }); + const result = await fetchMonitorCertificate(sslChecker, monitor); + expect(result).to.deep.equal({ validTo: "2022-01-01" }); + }); });