diff --git a/server/src/api/validation/index.ts b/server/src/api/validation/index.ts index 55c586769..9246395e4 100644 --- a/server/src/api/validation/index.ts +++ b/server/src/api/validation/index.ts @@ -22,3 +22,4 @@ export * from "./notificationValidation.js"; export * from "./userValidation.js"; export * from "./incidentValidation.js"; export * from "./tagValidation.js"; +export * from "./proxyValidation.js"; diff --git a/server/src/api/validation/monitorValidation.ts b/server/src/api/validation/monitorValidation.ts index b6ef0f498..65a64ed10 100644 --- a/server/src/api/validation/monitorValidation.ts +++ b/server/src/api/validation/monitorValidation.ts @@ -101,6 +101,16 @@ const refineHeadMatching = (body: { method?: string; useAdvancedMatching?: boole } }; +const refineProxySelection = (body: { proxyMode?: string; proxyId?: string }, ctx: z.RefinementCtx) => { + if (body.proxyMode === "custom" && !body.proxyId) { + ctx.addIssue({ + code: "custom", + path: ["proxyId"], + message: "A proxy must be selected when proxy mode is custom", + }); + } +}; + export const createMonitorBodyValidation = z .object({ _id: z.string().optional(), @@ -111,6 +121,8 @@ export const createMonitorBodyValidation = z statusWindowThreshold: z.number().min(1).max(100).default(60), url: z.string().min(1, "URL is required"), ignoreTlsErrors: z.boolean().default(false), + proxyMode: z.enum(ProxyModes).default("inherit"), + proxyId: z.string().optional(), useAdvancedMatching: z.boolean().default(false), port: z.number().optional(), isActive: z.boolean().optional(), @@ -141,7 +153,8 @@ export const createMonitorBodyValidation = z .superRefine(refineDnsHostname) .superRefine(refineStrategyType) .superRefine(refineHeadMatching) - .superRefine(refineRegexPattern); + .superRefine(refineRegexPattern) + .superRefine(refineProxySelection); export const editMonitorBodyValidation = z .object({ @@ -157,6 +170,8 @@ export const editMonitorBodyValidation = z customUpCodes: z.array(httpStatusCode).optional(), secret: z.string().optional(), ignoreTlsErrors: z.boolean().optional(), + proxyMode: z.enum(ProxyModes).optional(), + proxyId: z.string().optional(), useAdvancedMatching: z.boolean().optional(), jsonPath: z.union([z.string(), z.literal("")]).optional(), expectedValue: z.union([z.string(), z.literal("")]).optional(), @@ -181,7 +196,8 @@ export const editMonitorBodyValidation = z .superRefine(refineDnsHostname) .superRefine(refineStrategyType) .superRefine(refineHeadMatching) - .superRefine(refineRegexPattern); + .superRefine(refineRegexPattern) + .superRefine(refineProxySelection); export const pauseMonitorParamValidation = z.object({ monitorId: z.string().min(1, "Monitor ID is required"), @@ -256,7 +272,8 @@ const importedMonitorSchema = z .superRefine(refineDnsHostname) .superRefine(refineStrategyType) .superRefine(refineHeadMatching) - .superRefine(refineRegexPattern); + .superRefine(refineRegexPattern) + .superRefine(refineProxySelection); export const importMonitorsBodyValidation = z.object({ monitors: z.array(importedMonitorSchema).min(1, "At least one monitor is required"), @@ -288,6 +305,8 @@ export const monitorResponseSchema = z statusWindowSize: z.number(), statusWindowThreshold: z.number(), ignoreTlsErrors: z.boolean(), + proxyMode: z.enum(ProxyModes), + proxyId: z.string().optional(), useAdvancedMatching: z.boolean(), jsonPath: z.string().optional(), expectedValue: z.string().optional(), diff --git a/server/src/api/validation/proxyValidation.ts b/server/src/api/validation/proxyValidation.ts new file mode 100644 index 000000000..7e79a2542 --- /dev/null +++ b/server/src/api/validation/proxyValidation.ts @@ -0,0 +1,44 @@ +import { ProxyProtocols } from "@/domain/proxies/proxy.type.js"; +import { z } from "zod"; + +//**************************************** +// Proxy Validations +//**************************************** + +export const createProxyBodyValidation = z.object({ + name: z.string().min(1, "Proxy name is required"), + protocol: z.enum(ProxyProtocols, "Invalid proxy protocol"), + host: z.string().min(1, "Proxy host is required"), + port: z.number().int().min(1, "Proxy port is required").max(65535, "Proxy port must be between 1 and 65535"), + username: z.string().optional(), + password: z.string().optional(), +}); + +export const editProxyBodyValidation = createProxyBodyValidation; + +export const getProxyByIdParamValidation = z.object({ + id: z.string().min(1, "Proxy ID is required"), +}); +export const editProxyParamValidation = z.object({ + id: z.string().min(1, "Proxy ID is required"), +}); +export const deleteProxyParamValidation = z.object({ + id: z.string().min(1, "Proxy ID is required"), +}); + +// Canonical proxy shape returned by /proxies endpoints. Keep aligned with what +// the controllers actually serialize (password is intentionally omitted; hasPassword tells the client one is stored). +export const proxyResponseSchema = z + .object({ + id: z.string(), + teamId: z.string(), + name: z.string(), + protocol: z.enum(ProxyProtocols), + host: z.string(), + port: z.number(), + username: z.string().optional(), + hasPassword: z.boolean(), + createdAt: z.string(), + updatedAt: z.string(), + }) + .passthrough();