diff --git a/apps/nextjs/src/app/[locale]/manage/tools/certificates/hostnames/page.tsx b/apps/nextjs/src/app/[locale]/manage/tools/certificates/hostnames/page.tsx index 2b9aa6ac6..8eda3dcb5 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/certificates/hostnames/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/certificates/hostnames/page.tsx @@ -16,7 +16,7 @@ import { import { IconCertificateOff } from "@tabler/icons-react"; import { auth } from "@homarr/auth/next"; -import { getTrustedCertificateHostnamesAsync } from "@homarr/certificates/server"; +import { getTrustedCertificateHostnamesAsync } from "@homarr/core/infrastructure/certificates"; import { getI18n } from "@homarr/translation/server"; import { Link } from "@homarr/ui"; diff --git a/apps/nextjs/src/app/[locale]/manage/tools/certificates/page.tsx b/apps/nextjs/src/app/[locale]/manage/tools/certificates/page.tsx index fc9a29dd6..cf1730736 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/certificates/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/certificates/page.tsx @@ -5,8 +5,8 @@ import { IconAlertTriangle, IconCertificate, IconCertificateOff } from "@tabler/ import dayjs from "dayjs"; import { auth } from "@homarr/auth/next"; -import { loadCustomRootCertificatesAsync } from "@homarr/certificates/server"; import { getMantineColor } from "@homarr/common"; +import { loadCustomRootCertificatesAsync } from "@homarr/core/infrastructure/certificates"; import type { SupportedLanguage } from "@homarr/translation"; import { getI18n } from "@homarr/translation/server"; import { Link } from "@homarr/ui"; diff --git a/packages/api/src/router/certificates/certificate-router.ts b/packages/api/src/router/certificates/certificate-router.ts index ba758a6bb..cab6633f0 100644 --- a/packages/api/src/router/certificates/certificate-router.ts +++ b/packages/api/src/router/certificates/certificate-router.ts @@ -3,7 +3,10 @@ import { TRPCError } from "@trpc/server"; import { zfd } from "zod-form-data"; import { z } from "zod/v4"; -import { addCustomRootCertificateAsync, removeCustomRootCertificateAsync } from "@homarr/certificates/server"; +import { + addCustomRootCertificateAsync, + removeCustomRootCertificateAsync, +} from "@homarr/core/infrastructure/certificates"; import { createLogger } from "@homarr/core/infrastructure/logs"; import { and, eq } from "@homarr/db"; import { trustedCertificateHostnames } from "@homarr/db/schema"; diff --git a/packages/certificates/package.json b/packages/certificates/package.json index e63802146..b48ade91d 100644 --- a/packages/certificates/package.json +++ b/packages/certificates/package.json @@ -23,6 +23,7 @@ "prettier": "@homarr/prettier-config", "dependencies": { "@homarr/common": "workspace:^0.1.0", + "@homarr/core": "workspace:^0.1.0", "@homarr/db": "workspace:^0.1.0", "undici": "7.16.0" }, diff --git a/packages/certificates/src/server.ts b/packages/certificates/src/server.ts index a138f4039..81d7210b0 100644 --- a/packages/certificates/src/server.ts +++ b/packages/certificates/src/server.ts @@ -1,93 +1,18 @@ -import { X509Certificate } from "node:crypto"; -import fsSync from "node:fs"; -import fs from "node:fs/promises"; import type { AgentOptions } from "node:https"; import { Agent as HttpsAgent } from "node:https"; -import path from "node:path"; -import { checkServerIdentity, rootCertificates } from "node:tls"; +import { checkServerIdentity } from "node:tls"; import axios from "axios"; import type { RequestInfo, RequestInit, Response } from "undici"; import { fetch } from "undici"; -import { env } from "@homarr/common/env"; import { LoggingAgent } from "@homarr/common/server"; +import { + getAllTrustedCertificatesAsync, + getTrustedCertificateHostnamesAsync, +} from "@homarr/core/infrastructure/certificates"; import type { InferSelectModel } from "@homarr/db"; -import { db } from "@homarr/db"; import type { trustedCertificateHostnames } from "@homarr/db/schema"; -const getCertificateFolder = () => { - if (env.NODE_ENV !== "production") return process.env.LOCAL_CERTIFICATE_PATH; - return process.env.LOCAL_CERTIFICATE_PATH ?? path.join("/appdata", "trusted-certificates"); -}; - -export const loadCustomRootCertificatesAsync = async () => { - const folder = getCertificateFolder(); - - if (!folder) { - return []; - } - - if (!fsSync.existsSync(folder)) { - await fs.mkdir(folder, { recursive: true }); - } - - const dirContent = await fs.readdir(folder); - return await Promise.all( - dirContent - .filter((file) => file.endsWith(".crt") || file.endsWith(".pem")) - .map(async (file) => ({ - content: await fs.readFile(path.join(folder, file), "utf8"), - fileName: file, - })), - ); -}; - -export const removeCustomRootCertificateAsync = async (fileName: string) => { - const folder = getCertificateFolder(); - if (!folder) { - return null; - } - - const existingFiles = await fs.readdir(folder, { withFileTypes: true }); - if (!existingFiles.some((file) => file.isFile() && file.name === fileName)) { - throw new Error(`File ${fileName} does not exist`); - } - - const fullPath = path.join(folder, fileName); - const content = await fs.readFile(fullPath, "utf8"); - - await fs.rm(fullPath); - try { - return new X509Certificate(content); - } catch { - return null; - } -}; - -export const addCustomRootCertificateAsync = async (fileName: string, content: string) => { - const folder = getCertificateFolder(); - if (!folder) { - throw new Error( - "When you want to use custom certificates locally you need to set LOCAL_CERTIFICATE_PATH to an absolute path", - ); - } - - if (fileName.includes("/")) { - throw new Error("Invalid file name"); - } - - await fs.writeFile(path.join(folder, fileName), content); -}; - -export const getTrustedCertificateHostnamesAsync = async () => { - return await db.query.trustedCertificateHostnames.findMany(); -}; - -export const getAllTrustedCertificatesAsync = async () => { - const customCertificates = await loadCustomRootCertificatesAsync(); - return rootCertificates.concat(customCertificates.map((cert) => cert.content)); -}; - export const createCustomCheckServerIdentity = ( trustedHostnames: InferSelectModel[], ): typeof checkServerIdentity => { diff --git a/packages/core/package.json b/packages/core/package.json index ab752175a..7f5086c87 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -13,7 +13,11 @@ "./infrastructure/logs/error": "./src/infrastructure/logs/error.ts", "./infrastructure/db": "./src/infrastructure/db/index.ts", "./infrastructure/db/env": "./src/infrastructure/db/env.ts", - "./infrastructure/db/constants": "./src/infrastructure/db/constants.ts" + "./infrastructure/db/constants": "./src/infrastructure/db/constants.ts", + "./infrastructure/certificates": "./src/infrastructure/certificates/index.ts", + "./infrastructure/certificates/hostnames/db/sqlite": "./src/infrastructure/certificates/hostnames/db/sqlite.ts", + "./infrastructure/certificates/hostnames/db/mysql": "./src/infrastructure/certificates/hostnames/db/mysql.ts", + "./infrastructure/certificates/hostnames/db/postgresql": "./src/infrastructure/certificates/hostnames/db/postgresql.ts" }, "typesVersions": { "*": { diff --git a/packages/core/src/infrastructure/certificates/files/index.ts b/packages/core/src/infrastructure/certificates/files/index.ts new file mode 100644 index 000000000..fcc058ad9 --- /dev/null +++ b/packages/core/src/infrastructure/certificates/files/index.ts @@ -0,0 +1,74 @@ +import { X509Certificate } from "node:crypto"; +import fsSync from "node:fs"; +import fs from "node:fs/promises"; +import path from "node:path"; +import { rootCertificates } from "node:tls"; + +const getCertificateFolder = () => { + if (process.env.NODE_ENV !== "production") return process.env.LOCAL_CERTIFICATE_PATH; + return process.env.LOCAL_CERTIFICATE_PATH ?? path.join("/appdata", "trusted-certificates"); +}; + +export const loadCustomRootCertificatesAsync = async () => { + const folder = getCertificateFolder(); + + if (!folder) { + return []; + } + + if (!fsSync.existsSync(folder)) { + await fs.mkdir(folder, { recursive: true }); + } + + const dirContent = await fs.readdir(folder); + return await Promise.all( + dirContent + .filter((file) => file.endsWith(".crt") || file.endsWith(".pem")) + .map(async (file) => ({ + content: await fs.readFile(path.join(folder, file), "utf8"), + fileName: file, + })), + ); +}; + +export const getAllTrustedCertificatesAsync = async () => { + const customCertificates = await loadCustomRootCertificatesAsync(); + return rootCertificates.concat(customCertificates.map((cert) => cert.content)); +}; + +export const removeCustomRootCertificateAsync = async (fileName: string) => { + const folder = getCertificateFolder(); + if (!folder) { + return null; + } + + const existingFiles = await fs.readdir(folder, { withFileTypes: true }); + if (!existingFiles.some((file) => file.isFile() && file.name === fileName)) { + throw new Error(`File ${fileName} does not exist`); + } + + const fullPath = path.join(folder, fileName); + const content = await fs.readFile(fullPath, "utf8"); + + await fs.rm(fullPath); + try { + return new X509Certificate(content); + } catch { + return null; + } +}; + +export const addCustomRootCertificateAsync = async (fileName: string, content: string) => { + const folder = getCertificateFolder(); + if (!folder) { + throw new Error( + "When you want to use custom certificates locally you need to set LOCAL_CERTIFICATE_PATH to an absolute path", + ); + } + + if (fileName.includes("/")) { + throw new Error("Invalid file name"); + } + + await fs.writeFile(path.join(folder, fileName), content); +}; diff --git a/packages/core/src/infrastructure/certificates/hostnames/db/mysql.ts b/packages/core/src/infrastructure/certificates/hostnames/db/mysql.ts new file mode 100644 index 000000000..01dde026d --- /dev/null +++ b/packages/core/src/infrastructure/certificates/hostnames/db/mysql.ts @@ -0,0 +1,15 @@ +import { mysqlTable, primaryKey, text, varchar } from "drizzle-orm/mysql-core"; + +export const trustedCertificateHostnames = mysqlTable( + "trusted_certificate_hostname", + { + hostname: varchar({ length: 256 }).notNull(), + thumbprint: varchar({ length: 128 }).notNull(), + certificate: text().notNull(), + }, + (table) => ({ + compoundKey: primaryKey({ + columns: [table.hostname, table.thumbprint], + }), + }), +); diff --git a/packages/core/src/infrastructure/certificates/hostnames/db/postgresql.ts b/packages/core/src/infrastructure/certificates/hostnames/db/postgresql.ts new file mode 100644 index 000000000..6e89bd392 --- /dev/null +++ b/packages/core/src/infrastructure/certificates/hostnames/db/postgresql.ts @@ -0,0 +1,15 @@ +import { pgTable, primaryKey, text, varchar } from "drizzle-orm/pg-core"; + +export const trustedCertificateHostnames = pgTable( + "trusted_certificate_hostname", + { + hostname: varchar({ length: 256 }).notNull(), + thumbprint: varchar({ length: 128 }).notNull(), + certificate: text().notNull(), + }, + (table) => ({ + compoundKey: primaryKey({ + columns: [table.hostname, table.thumbprint], + }), + }), +); diff --git a/packages/core/src/infrastructure/certificates/hostnames/db/schema.ts b/packages/core/src/infrastructure/certificates/hostnames/db/schema.ts new file mode 100644 index 000000000..4bea4a61b --- /dev/null +++ b/packages/core/src/infrastructure/certificates/hostnames/db/schema.ts @@ -0,0 +1,10 @@ +import { createSchema } from "../../../db"; +import * as mysql from "./mysql"; +import * as postgresql from "./postgresql"; +import * as sqlite from "./sqlite"; + +export const schema = createSchema({ + "better-sqlite3": () => sqlite, + mysql2: () => mysql, + "node-postgres": () => postgresql, +}); diff --git a/packages/core/src/infrastructure/certificates/hostnames/db/sqlite.ts b/packages/core/src/infrastructure/certificates/hostnames/db/sqlite.ts new file mode 100644 index 000000000..6ad036139 --- /dev/null +++ b/packages/core/src/infrastructure/certificates/hostnames/db/sqlite.ts @@ -0,0 +1,15 @@ +import { primaryKey, sqliteTable, text } from "drizzle-orm/sqlite-core"; + +export const trustedCertificateHostnames = sqliteTable( + "trusted_certificate_hostname", + { + hostname: text().notNull(), + thumbprint: text().notNull(), + certificate: text().notNull(), + }, + (table) => ({ + compoundKey: primaryKey({ + columns: [table.hostname, table.thumbprint], + }), + }), +); diff --git a/packages/core/src/infrastructure/certificates/hostnames/index.ts b/packages/core/src/infrastructure/certificates/hostnames/index.ts new file mode 100644 index 000000000..46cc86b7f --- /dev/null +++ b/packages/core/src/infrastructure/certificates/hostnames/index.ts @@ -0,0 +1,8 @@ +import { createDb } from "../../db"; +import { schema } from "./db/schema"; + +const db = createDb(schema); + +export const getTrustedCertificateHostnamesAsync = async () => { + return await db.query.trustedCertificateHostnames.findMany(); +}; diff --git a/packages/core/src/infrastructure/certificates/index.ts b/packages/core/src/infrastructure/certificates/index.ts new file mode 100644 index 000000000..fb4ecaa0b --- /dev/null +++ b/packages/core/src/infrastructure/certificates/index.ts @@ -0,0 +1,7 @@ +export { getTrustedCertificateHostnamesAsync } from "./hostnames"; +export { + addCustomRootCertificateAsync, + removeCustomRootCertificateAsync, + getAllTrustedCertificatesAsync, + loadCustomRootCertificatesAsync, +} from "./files"; diff --git a/packages/db/schema/mysql.ts b/packages/db/schema/mysql.ts index 2a0b042e7..edbcec281 100644 --- a/packages/db/schema/mysql.ts +++ b/packages/db/schema/mysql.ts @@ -46,6 +46,8 @@ const customBlob = customType<{ data: Buffer }>({ }, }); +export * from "@homarr/core/infrastructure/certificates/hostnames/db/mysql"; + export const apiKeys = mysqlTable("apiKey", { id: varchar({ length: 64 }).notNull().primaryKey(), apiKey: text().notNull(), @@ -495,20 +497,6 @@ export const onboarding = mysqlTable("onboarding", { previousStep: varchar({ length: 64 }).$type(), }); -export const trustedCertificateHostnames = mysqlTable( - "trusted_certificate_hostname", - { - hostname: varchar({ length: 256 }).notNull(), - thumbprint: varchar({ length: 128 }).notNull(), - certificate: text().notNull(), - }, - (table) => ({ - compoundKey: primaryKey({ - columns: [table.hostname, table.thumbprint], - }), - }), -); - export const cronJobConfigurations = mysqlTable("cron_job_configuration", { name: varchar({ length: 256 }).notNull().primaryKey(), cronExpression: varchar({ length: 32 }).notNull(), diff --git a/packages/db/schema/postgresql.ts b/packages/db/schema/postgresql.ts index 5a48ce4e8..e41d72f75 100644 --- a/packages/db/schema/postgresql.ts +++ b/packages/db/schema/postgresql.ts @@ -45,6 +45,8 @@ const customBlob = customType<{ data: Buffer }>({ }, }); +export * from "@homarr/core/infrastructure/certificates/hostnames/db/postgresql"; + export const apiKeys = pgTable("apiKey", { id: varchar({ length: 64 }).notNull().primaryKey(), apiKey: text().notNull(), @@ -494,20 +496,6 @@ export const onboarding = pgTable("onboarding", { previousStep: varchar({ length: 64 }).$type(), }); -export const trustedCertificateHostnames = pgTable( - "trusted_certificate_hostname", - { - hostname: varchar({ length: 256 }).notNull(), - thumbprint: varchar({ length: 128 }).notNull(), - certificate: text().notNull(), - }, - (table) => ({ - compoundKey: primaryKey({ - columns: [table.hostname, table.thumbprint], - }), - }), -); - export const cronJobConfigurations = pgTable("cron_job_configuration", { name: varchar({ length: 256 }).notNull().primaryKey(), cronExpression: varchar({ length: 32 }).notNull(), diff --git a/packages/db/schema/sqlite.ts b/packages/db/schema/sqlite.ts index e7b6d4d9f..78ec4ac9c 100644 --- a/packages/db/schema/sqlite.ts +++ b/packages/db/schema/sqlite.ts @@ -28,6 +28,8 @@ import type { WidgetKind, } from "@homarr/definitions"; +export * from "@homarr/core/infrastructure/certificates/hostnames/db/sqlite"; + export const apiKeys = sqliteTable("apiKey", { id: text().notNull().primaryKey(), apiKey: text().notNull(), @@ -480,20 +482,6 @@ export const onboarding = sqliteTable("onboarding", { previousStep: text().$type(), }); -export const trustedCertificateHostnames = sqliteTable( - "trusted_certificate_hostname", - { - hostname: text().notNull(), - thumbprint: text().notNull(), - certificate: text().notNull(), - }, - (table) => ({ - compoundKey: primaryKey({ - columns: [table.hostname, table.thumbprint], - }), - }), -); - export const cronJobConfigurations = sqliteTable("cron_job_configuration", { name: text().notNull().primaryKey(), cronExpression: text().notNull(), diff --git a/packages/integrations/src/base/test-connection/test-connection-service.ts b/packages/integrations/src/base/test-connection/test-connection-service.ts index bedc603f7..0a1a7e88b 100644 --- a/packages/integrations/src/base/test-connection/test-connection-service.ts +++ b/packages/integrations/src/base/test-connection/test-connection-service.ts @@ -1,12 +1,12 @@ import type { X509Certificate } from "node:crypto"; import tls from "node:tls"; +import { createCustomCheckServerIdentity } from "@homarr/certificates/server"; +import { getPortFromUrl } from "@homarr/common"; import { - createCustomCheckServerIdentity, getAllTrustedCertificatesAsync, getTrustedCertificateHostnamesAsync, -} from "@homarr/certificates/server"; -import { getPortFromUrl } from "@homarr/common"; +} from "@homarr/core/infrastructure/certificates"; import { createLogger } from "@homarr/core/infrastructure/logs"; import type { IntegrationRequestErrorOfType } from "../errors/http/integration-request-error"; diff --git a/packages/integrations/src/unifi-controller/unifi-controller-integration.ts b/packages/integrations/src/unifi-controller/unifi-controller-integration.ts index f47e18ff7..b4a688e63 100644 --- a/packages/integrations/src/unifi-controller/unifi-controller-integration.ts +++ b/packages/integrations/src/unifi-controller/unifi-controller-integration.ts @@ -2,12 +2,12 @@ import type tls from "node:tls"; import axios from "axios"; import { HttpCookieAgent, HttpsCookieAgent } from "http-cookie-agent/http"; +import { createCustomCheckServerIdentity } from "@homarr/certificates/server"; +import { getPortFromUrl } from "@homarr/common"; import { - createCustomCheckServerIdentity, getAllTrustedCertificatesAsync, getTrustedCertificateHostnamesAsync, -} from "@homarr/certificates/server"; -import { getPortFromUrl } from "@homarr/common"; +} from "@homarr/core/infrastructure/certificates"; import type { SiteStats } from "@homarr/node-unifi"; import Unifi from "@homarr/node-unifi"; diff --git a/packages/integrations/test/aria2.spec.ts b/packages/integrations/test/aria2.spec.ts index 5018ccfdd..a204d7ccf 100644 --- a/packages/integrations/test/aria2.spec.ts +++ b/packages/integrations/test/aria2.spec.ts @@ -14,6 +14,16 @@ vi.mock("@homarr/db", async (importActual) => { db: createDb(), }; }); +vi.mock("@homarr/core/infrastructure/certificates", async (importActual) => { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const actual = await importActual(); + return { + ...actual, + getTrustedCertificateHostnamesAsync: vi.fn().mockImplementation(() => { + return Promise.resolve([]); + }), + }; +}); const API_KEY = "ARIA2_API_KEY"; const IMAGE_NAME = "hurlenko/aria2-ariang:latest"; diff --git a/packages/integrations/test/base.spec.ts b/packages/integrations/test/base.spec.ts index 9001a0410..4fcce3458 100644 --- a/packages/integrations/test/base.spec.ts +++ b/packages/integrations/test/base.spec.ts @@ -16,6 +16,17 @@ vi.mock("@homarr/db", async (importActual) => { }; }); +vi.mock("@homarr/core/infrastructure/certificates", async (importActual) => { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const actual = await importActual(); + return { + ...actual, + getTrustedCertificateHostnamesAsync: vi.fn().mockImplementation(() => { + return Promise.resolve([]); + }), + }; +}); + describe("Base integration", () => { test("testConnectionAsync should handle errors", async () => { const responseError = new ResponseError({ status: 500, url: "https://example.com" }); diff --git a/packages/integrations/test/home-assistant.spec.ts b/packages/integrations/test/home-assistant.spec.ts index 127e91556..608af1ce0 100644 --- a/packages/integrations/test/home-assistant.spec.ts +++ b/packages/integrations/test/home-assistant.spec.ts @@ -17,6 +17,17 @@ vi.mock("@homarr/db", async (importActual) => { }; }); +vi.mock("@homarr/core/infrastructure/certificates", async (importActual) => { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const actual = await importActual(); + return { + ...actual, + getTrustedCertificateHostnamesAsync: vi.fn().mockImplementation(() => { + return Promise.resolve([]); + }), + }; +}); + const DEFAULT_API_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkNjQwY2VjNDFjOGU0NGM5YmRlNWQ4ZmFjMjUzYWViZiIsImlhdCI6MTcxODQ3MTE1MSwiZXhwIjoyMDMzODMxMTUxfQ.uQCZ5FZTokipa6N27DtFhLHkwYEXU1LZr0fsVTryL2Q"; const IMAGE_NAME = "ghcr.io/home-assistant/home-assistant:stable"; diff --git a/packages/integrations/test/nzbget.spec.ts b/packages/integrations/test/nzbget.spec.ts index e0f77a124..6b485e71d 100644 --- a/packages/integrations/test/nzbget.spec.ts +++ b/packages/integrations/test/nzbget.spec.ts @@ -18,6 +18,17 @@ vi.mock("@homarr/db", async (importActual) => { }; }); +vi.mock("@homarr/core/infrastructure/certificates", async (importActual) => { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const actual = await importActual(); + return { + ...actual, + getTrustedCertificateHostnamesAsync: vi.fn().mockImplementation(() => { + return Promise.resolve([]); + }), + }; +}); + const username = "nzbget"; const password = "tegbzn6789"; const IMAGE_NAME = "linuxserver/nzbget:latest"; diff --git a/packages/integrations/test/pi-hole.spec.ts b/packages/integrations/test/pi-hole.spec.ts index 837f7ba08..78c272689 100644 --- a/packages/integrations/test/pi-hole.spec.ts +++ b/packages/integrations/test/pi-hole.spec.ts @@ -17,6 +17,17 @@ vi.mock("@homarr/db", async (importActual) => { }; }); +vi.mock("@homarr/core/infrastructure/certificates", async (importActual) => { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const actual = await importActual(); + return { + ...actual, + getTrustedCertificateHostnamesAsync: vi.fn().mockImplementation(() => { + return Promise.resolve([]); + }), + }; +}); + const DEFAULT_PASSWORD = "12341234"; const DEFAULT_API_KEY = "3b1434980677dcf53fa8c4a611db3b1f0f88478790097515c0abb539102778b9"; // Some hash generated from password diff --git a/packages/integrations/test/sabnzbd.spec.ts b/packages/integrations/test/sabnzbd.spec.ts index af8c6dcdb..8603f04a0 100644 --- a/packages/integrations/test/sabnzbd.spec.ts +++ b/packages/integrations/test/sabnzbd.spec.ts @@ -18,6 +18,17 @@ vi.mock("@homarr/db", async (importActual) => { }; }); +vi.mock("@homarr/core/infrastructure/certificates", async (importActual) => { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const actual = await importActual(); + return { + ...actual, + getTrustedCertificateHostnamesAsync: vi.fn().mockImplementation(() => { + return Promise.resolve([]); + }), + }; +}); + const DEFAULT_API_KEY = "8r45mfes43s3iw7x3oecto6dl9ilxnf9"; const IMAGE_NAME = "linuxserver/sabnzbd:latest"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f774c9aaa..783bf4860 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -800,6 +800,9 @@ importers: '@homarr/common': specifier: workspace:^0.1.0 version: link:../common + '@homarr/core': + specifier: workspace:^0.1.0 + version: link:../core '@homarr/db': specifier: workspace:^0.1.0 version: link:../db