From ea698b6558074c8148e9cd95cdd4019592a1e2ec Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Wed, 4 Mar 2026 15:45:38 -0500 Subject: [PATCH] feat: add HTTP basic authentication support for monitors (#3033) ## Changes - Add basicAuthUser and basicAuthPassword fields to Monitor model, types, and validation (server + client) - Update networkService to send Authorization: Basic header when credentials are provided - Add basic auth form fields (username/password) to the create/edit monitor page for HTTP monitors - Add i18n strings for the new basic authentication section ## Benefits - Users can monitor endpoints protected by HTTP basic authentication - Credentials are sent as standard Authorization: Basic base64-encoded header - Basic auth is only used when bearer token (secret) is not configured --- client/src/Hooks/useMonitorForm.ts | 4 ++ client/src/Pages/CreateMonitor/index.tsx | 44 +++++++++++++++++++ client/src/Types/Monitor.ts | 2 + client/src/Validation/monitor.ts | 2 + client/src/locales/en.json | 12 +++++ server/src/db/models/Monitor.ts | 6 +++ .../service/infrastructure/networkService.ts | 11 ++++- server/src/types/monitor.ts | 2 + server/src/validation/monitorValidation.ts | 4 ++ 9 files changed, 85 insertions(+), 2 deletions(-) diff --git a/client/src/Hooks/useMonitorForm.ts b/client/src/Hooks/useMonitorForm.ts index 8b5252879..4d39d9de2 100644 --- a/client/src/Hooks/useMonitorForm.ts +++ b/client/src/Hooks/useMonitorForm.ts @@ -40,6 +40,8 @@ export const useMonitorForm = ({ matchMethod: data?.matchMethod || "", expectedValue: data?.expectedValue || "", jsonPath: data?.jsonPath || "", + basicAuthUser: data?.basicAuthUser || "", + basicAuthPassword: data?.basicAuthPassword || "", }; break; case "ping": @@ -113,6 +115,8 @@ export const useMonitorForm = ({ matchMethod: "", expectedValue: "", jsonPath: "", + basicAuthUser: "", + basicAuthPassword: "", }; } diff --git a/client/src/Pages/CreateMonitor/index.tsx b/client/src/Pages/CreateMonitor/index.tsx index 34e141795..63e184364 100644 --- a/client/src/Pages/CreateMonitor/index.tsx +++ b/client/src/Pages/CreateMonitor/index.tsx @@ -770,6 +770,50 @@ const CreateMonitorPage = () => { /> )} + {watchedType === "http" && ( + + ( + + )} + /> + ( + + )} + /> + + } + /> + )} + {watchedType === "http" && ( ( type: String, enum: ["equal", "include", "regex", ""], }, + basicAuthUser: { + type: String, + }, + basicAuthPassword: { + type: String, + }, url: { type: String, required: true, diff --git a/server/src/service/infrastructure/networkService.ts b/server/src/service/infrastructure/networkService.ts index 702affed2..29db5ac69 100644 --- a/server/src/service/infrastructure/networkService.ts +++ b/server/src/service/infrastructure/networkService.ts @@ -296,7 +296,7 @@ class NetworkService implements INetworkService { } private async requestHttp(monitor: Monitor): Promise> { - const { url, secret, ignoreTlsErrors, useAdvancedMatching, jsonPath, matchMethod, expectedValue } = monitor; + const { url, secret, ignoreTlsErrors, useAdvancedMatching, jsonPath, matchMethod, expectedValue, basicAuthUser, basicAuthPassword } = monitor; const httpResponse = this.buildStatusResponse({ monitor, overrides: { @@ -310,8 +310,15 @@ class NetworkService implements INetworkService { if (!url) { throw new Error("Monitor URL is required"); } + const headers: Record = {}; + if (secret) { + headers["Authorization"] = `Bearer ${secret}`; + } else if (basicAuthUser) { + const credentials = Buffer.from(`${basicAuthUser}:${basicAuthPassword || ""}`).toString("base64"); + headers["Authorization"] = `Basic ${credentials}`; + } const config: Record = { - headers: secret ? { Authorization: `Bearer ${secret}` } : undefined, + headers: Object.keys(headers).length > 0 ? headers : undefined, }; if (ignoreTlsErrors) { diff --git a/server/src/types/monitor.ts b/server/src/types/monitor.ts index a6ecc4ef4..2601c64bd 100644 --- a/server/src/types/monitor.ts +++ b/server/src/types/monitor.ts @@ -27,6 +27,8 @@ export interface Monitor { jsonPath?: string; expectedValue?: string; matchMethod?: MonitorMatchMethod; + basicAuthUser?: string; + basicAuthPassword?: string; url: string; port?: number; isActive: boolean; diff --git a/server/src/validation/monitorValidation.ts b/server/src/validation/monitorValidation.ts index 4694143ac..593d7e3c9 100644 --- a/server/src/validation/monitorValidation.ts +++ b/server/src/validation/monitorValidation.ts @@ -70,6 +70,8 @@ export const createMonitorBodyValidation = z.object({ jsonPath: z.union([z.string(), z.literal("")]).optional(), expectedValue: z.union([z.string(), z.literal("")]).optional(), matchMethod: z.union([z.string(), z.null(), z.literal("")]).optional(), + basicAuthUser: z.union([z.string(), z.literal("")]).optional(), + basicAuthPassword: z.union([z.string(), z.literal("")]).optional(), gameId: z.union([z.string(), z.literal("")]).optional(), grpcServiceName: z.union([z.string(), z.literal("")]).default(""), selectedDisks: z.array(z.string()).optional(), @@ -94,6 +96,8 @@ export const editMonitorBodyValidation = z.object({ jsonPath: z.union([z.string(), z.literal("")]).optional(), expectedValue: z.union([z.string(), z.literal("")]).optional(), matchMethod: z.union([z.string(), z.null(), z.literal("")]).optional(), + basicAuthUser: z.union([z.string(), z.literal("")]).optional(), + basicAuthPassword: z.union([z.string(), z.literal("")]).optional(), port: z.number().min(1).max(65535).optional(), cpuAlertThreshold: z.number().optional(), memoryAlertThreshold: z.number().optional(),