fix(backend): validate customUpCodes against standard HTTP status codes
Addresses reviewer feedback by enforcing strict validation on the array. Previously, the Zod schema accepted any arbitrary number, which could allow invalid or self-defined HTTP codes (e.g., 5000) to be saved to the database. Refs #3657
This commit is contained in:
1 parent
4e31499f78
commit
87a6640c1d
2 files changed
+97
-4
No files matched your search
@@ -2,6 +2,9 @@ import { z } from "zod";
|
||||
import { booleanCoercion, dnsHostnameRegex, dnsServerValidation } from "./shared.js";
|
||||
import { GeoContinents } from "@/types/geoCheck.js";
|
||||
import { DnsRecordTypes, MonitorMatchMethods, MonitorStatuses, MonitorTypes, PageSpeedStrategies } from "@/types/monitor.js";
|
||||
import http from "node:http";
|
||||
|
||||
const httpStatusCode = z.number().refine((code) => code.toString() in http.STATUS_CODES, { message: "Must be a valid HTTP status code" });
|
||||
|
||||
export const getMonitorByIdParamValidation = z.object({
|
||||
monitorId: z.string().min(1, "Monitor ID is required"),
|
||||
@@ -81,7 +84,7 @@ export const createMonitorBodyValidation = z
|
||||
tempAlertThreshold: z.number().optional(),
|
||||
notifications: z.array(z.string()).optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
customUpCodes: z.array(z.number()).default([]),
|
||||
customUpCodes: z.array(httpStatusCode).default([]),
|
||||
secret: z.string().optional(),
|
||||
jsonPath: z.union([z.string(), z.literal("")]).optional(),
|
||||
expectedValue: z.union([z.string(), z.literal("")]).optional(),
|
||||
@@ -111,7 +114,7 @@ export const editMonitorBodyValidation = z
|
||||
interval: z.number().optional(),
|
||||
notifications: z.array(z.string()).optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
customUpCodes: z.array(z.number()).optional(),
|
||||
customUpCodes: z.array(httpStatusCode).optional(),
|
||||
secret: z.string().optional(),
|
||||
ignoreTlsErrors: z.boolean().optional(),
|
||||
useAdvancedMatching: z.boolean().optional(),
|
||||
@@ -182,7 +185,7 @@ const importedMonitorSchema = z
|
||||
uptimePercentage: z.number().optional(),
|
||||
notifications: z.array(z.string()).default([]),
|
||||
tags: z.array(z.string()).default([]),
|
||||
customUpCodes: z.array(z.number()).default([]),
|
||||
customUpCodes: z.array(httpStatusCode).default([]),
|
||||
secret: z.string().optional(),
|
||||
cpuAlertThreshold: z.number().default(100),
|
||||
cpuAlertCounter: z.number().default(5),
|
||||
@@ -244,7 +247,7 @@ export const monitorResponseSchema = z
|
||||
matchMethod: z.enum(MonitorMatchMethods).optional(),
|
||||
notifications: z.array(z.string()),
|
||||
tags: z.array(z.string()),
|
||||
customUpCodes: z.array(z.number()).optional(),
|
||||
customUpCodes: z.array(httpStatusCode).optional(),
|
||||
secret: z.string().optional(),
|
||||
cpuAlertThreshold: z.number(),
|
||||
memoryAlertThreshold: z.number(),
|
||||
|
||||
@@ -222,3 +222,93 @@ describe("monitorValidation — strategy gating", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("monitorValidation — customUpCodes", () => {
|
||||
const baseHttpBody = {
|
||||
name: "HTTP check",
|
||||
type: "http" as const,
|
||||
url: "https://example.com",
|
||||
};
|
||||
|
||||
describe("createMonitorBodyValidation", () => {
|
||||
it("accepts valid HTTP status codes", () => {
|
||||
const parsed = createMonitorBodyValidation.parse({
|
||||
...baseHttpBody,
|
||||
customUpCodes: [200, 301, 404, 503],
|
||||
});
|
||||
expect(parsed.customUpCodes).toEqual([200, 301, 404, 503]);
|
||||
});
|
||||
|
||||
it("rejects invalid HTTP status codes", () => {
|
||||
expect(() =>
|
||||
createMonitorBodyValidation.parse({
|
||||
...baseHttpBody,
|
||||
customUpCodes: [5000],
|
||||
})
|
||||
).toThrow();
|
||||
|
||||
expect(() =>
|
||||
createMonitorBodyValidation.parse({
|
||||
...baseHttpBody,
|
||||
customUpCodes: [-1],
|
||||
})
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it("defaults to an empty array when not provided", () => {
|
||||
const parsed = createMonitorBodyValidation.parse(baseHttpBody);
|
||||
expect(parsed.customUpCodes).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("editMonitorBodyValidation", () => {
|
||||
it("accepts valid HTTP status codes on edits", () => {
|
||||
const parsed = editMonitorBodyValidation.parse({
|
||||
customUpCodes: [200, 201],
|
||||
});
|
||||
expect(parsed.customUpCodes).toEqual([200, 201]);
|
||||
});
|
||||
|
||||
it("rejects invalid HTTP status codes on edits", () => {
|
||||
expect(() =>
|
||||
editMonitorBodyValidation.parse({
|
||||
customUpCodes: [9999],
|
||||
})
|
||||
).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("importMonitorsBodyValidation", () => {
|
||||
it("retains valid HTTP status codes on imported HTTP monitors", () => {
|
||||
const parsed = importMonitorsBodyValidation.parse({
|
||||
monitors: [
|
||||
{
|
||||
...baseHttpBody,
|
||||
customUpCodes: [404],
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(parsed.monitors[0].customUpCodes).toEqual([404]);
|
||||
});
|
||||
|
||||
it("rejects invalid HTTP status codes on imported HTTP monitors", () => {
|
||||
expect(() =>
|
||||
importMonitorsBodyValidation.parse({
|
||||
monitors: [
|
||||
{
|
||||
...baseHttpBody,
|
||||
customUpCodes: [5000],
|
||||
},
|
||||
],
|
||||
})
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it("defaults to an empty array when not provided on import", () => {
|
||||
const parsed = importMonitorsBodyValidation.parse({
|
||||
monitors: [baseHttpBody],
|
||||
});
|
||||
expect(parsed.monitors[0].customUpCodes).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user