From c89e35e13c794bbdf98748c00644b86f7f5dbe68 Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Tue, 18 Nov 2025 21:05:00 +0100 Subject: [PATCH] feat(pi-hole): support no-auth (#4480) --- packages/definitions/src/integration.ts | 2 +- .../src/pi-hole/v6/pi-hole-integration-v6.ts | 44 +++++++++++++------ .../src/pi-hole/v6/pi-hole-schemas-v6.ts | 1 + 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/packages/definitions/src/integration.ts b/packages/definitions/src/integration.ts index 61abd39fd..1309796d5 100644 --- a/packages/definitions/src/integration.ts +++ b/packages/definitions/src/integration.ts @@ -145,7 +145,7 @@ export const integrationDefs = { }, piHole: { name: "Pi-hole", - secretKinds: [["apiKey"]], + secretKinds: [["apiKey"], []], iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/pi-hole.svg", category: ["dnsHole"], documentationUrl: createDocumentationLink("/docs/integrations/pi-hole"), diff --git a/packages/integrations/src/pi-hole/v6/pi-hole-integration-v6.ts b/packages/integrations/src/pi-hole/v6/pi-hole-integration-v6.ts index 2332d47bc..cf9b63599 100644 --- a/packages/integrations/src/pi-hole/v6/pi-hole-integration-v6.ts +++ b/packages/integrations/src/pi-hole/v6/pi-hole-integration-v6.ts @@ -17,7 +17,7 @@ import { dnsBlockingGetSchema, sessionResponseSchema, statsSummaryGetSchema } fr const localLogger = logger.child({ module: "PiHoleIntegrationV6" }); export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIntegration { - private readonly sessionStore: SessionStore; + private readonly sessionStore: SessionStore<{ sid: string | null }>; constructor(integration: IntegrationInput) { super(integration); @@ -28,7 +28,7 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn const response = await this.withAuthAsync(async (sessionId) => { return await fetchWithTrustedCertificatesAsync(this.url("/api/dns/blocking"), { headers: { - sid: sessionId, + sid: sessionId ?? undefined, }, }); }); @@ -46,7 +46,7 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn const response = await this.withAuthAsync(async (sessionId) => { return fetchWithTrustedCertificatesAsync(this.url("/api/stats/summary"), { headers: { - sid: sessionId, + sid: sessionId ?? undefined, }, }); }); @@ -85,7 +85,7 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn const response = await this.withAuthAsync(async (sessionId) => { return await fetchWithTrustedCertificatesAsync(this.url("/api/dns/blocking"), { headers: { - sid: sessionId, + sid: sessionId ?? undefined, }, body: JSON.stringify({ blocking: true }), method: "POST", @@ -101,7 +101,7 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn const response = await this.withAuthAsync(async (sessionId) => { return await fetchWithTrustedCertificatesAsync(this.url("/api/dns/blocking"), { headers: { - sid: sessionId, + sid: sessionId ?? undefined, }, body: JSON.stringify({ blocking: false, timer: duration }), method: "POST", @@ -118,12 +118,16 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn * @param callback * @returns */ - private async withAuthAsync(callback: (sessionId: string) => Promise) { + private async withAuthAsync(callback: (sessionId: string | null) => Promise) { + if (!super.hasSecretValue("apiKey")) { + return await callback(null); + } + const storedSession = await this.sessionStore.getAsync(); if (storedSession) { localLogger.debug("Using stored session for request", { integrationId: this.integration.id }); - const response = await callback(storedSession); + const response = await callback(storedSession.sid); if (response.status !== 401) { return response; } @@ -132,7 +136,7 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn } const sessionId = await this.getSessionAsync(); - await this.sessionStore.setAsync(sessionId); + await this.sessionStore.setAsync({ sid: sessionId }); const response = await callback(sessionId); return response; } @@ -141,11 +145,13 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn * Get a session id from the Pi-hole server * @returns The session id */ - private async getSessionAsync(fetchAsync: typeof undiciFetch = fetchWithTrustedCertificatesAsync): Promise { - const apiKey = super.getSecretValue("apiKey"); + private async getSessionAsync( + fetchAsync: typeof undiciFetch = fetchWithTrustedCertificatesAsync, + ): Promise { + const apiKey = super.hasSecretValue("apiKey") ? super.getSecretValue("apiKey") : null; const response = await fetchAsync(this.url("/api/auth"), { method: "POST", - body: JSON.stringify({ password: apiKey }), + body: JSON.stringify({ password: apiKey ?? "" }), headers: { "User-Agent": "Homarr", }, @@ -156,8 +162,13 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn const data = await response.json(); const result = await sessionResponseSchema.parseAsync(data); - if (!result.session.sid) { - throw new ResponseError({ status: 401, url: response.url }); + if (!result.session.valid) { + throw new ResponseError( + { status: 401, url: response.url }, + { + cause: result.session.message ? new Error(result.session.message) : undefined, + }, + ); } localLogger.info("Received session id successfully", { integrationId: this.integration.id }); @@ -170,9 +181,14 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn * @param sessionId The session id to remove */ private async clearSessionAsync( - sessionId: string, + sessionId: string | null, fetchAsync: typeof undiciFetch = fetchWithTrustedCertificatesAsync, ) { + if (!sessionId) { + localLogger.debug("No session id to clear"); + return; + } + const response = await fetchAsync(this.url("/api/auth"), { method: "DELETE", headers: { diff --git a/packages/integrations/src/pi-hole/v6/pi-hole-schemas-v6.ts b/packages/integrations/src/pi-hole/v6/pi-hole-schemas-v6.ts index 3be133aa9..34c7ea3c3 100644 --- a/packages/integrations/src/pi-hole/v6/pi-hole-schemas-v6.ts +++ b/packages/integrations/src/pi-hole/v6/pi-hole-schemas-v6.ts @@ -2,6 +2,7 @@ import { z } from "zod/v4"; export const sessionResponseSchema = z.object({ session: z.object({ + valid: z.boolean(), sid: z.string().nullable(), message: z.string().nullable(), }),