tighten validation, replace negative type checks with allowed list
This commit is contained in:
1 parent
08043a5ea0
commit
766d847561
5 files changed
+37
-15
No files matched your search
@@ -124,9 +124,10 @@ const websocketSchema = baseSchema.extend({
|
||||
});
|
||||
|
||||
// Hostname (FQDN) — labels of 1-63 alphanumerics/hyphens separated by dots,
|
||||
// no scheme, port, path, or whitespace. Total length ≤ 253.
|
||||
// optionally prefixed with `_` for service labels (e.g. _dmarc, _imaps._tcp).
|
||||
// No scheme, port, path, or whitespace. Total length ≤ 253.
|
||||
const hostnameRegex =
|
||||
/^(?=.{1,253}$)([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,63}$/;
|
||||
/^(?=.{1,253}$)([a-zA-Z0-9_](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,63}$/;
|
||||
|
||||
// DNS monitor schema
|
||||
const dnsSchema = baseSchema.extend({
|
||||
|
||||
@@ -9,7 +9,8 @@ import type {
|
||||
GamesMap,
|
||||
GroupedGeoCheckResult,
|
||||
} from "@/types/monitor.js";
|
||||
import { supportsGeoCheck } from "@/types/monitor.js";
|
||||
import { supportsGeoCheck, supportsUptimeDetails } from "@/types/monitor.js";
|
||||
import type { UptimeChecksResult, HardwareChecksResult, PageSpeedChecksResult } from "@/types/check.js";
|
||||
import type { GeoContinent } from "@/types/geoCheck.js";
|
||||
import type {
|
||||
IChecksRepository,
|
||||
@@ -28,6 +29,9 @@ import { ILogger } from "@/utils/logger.js";
|
||||
const SERVICE_NAME = "MonitorService";
|
||||
type DateRangeKey = "recent" | "day" | "week" | "month" | "all";
|
||||
|
||||
const isUptimeChecksResult = (result: UptimeChecksResult | HardwareChecksResult | PageSpeedChecksResult): result is UptimeChecksResult =>
|
||||
supportsUptimeDetails(result.monitorType);
|
||||
|
||||
export interface IMonitorService {
|
||||
readonly serviceName: string;
|
||||
|
||||
@@ -216,16 +220,7 @@ export class MonitorService implements IMonitorService {
|
||||
});
|
||||
const monitorStats = await this.monitorStatsRepository.findByMonitorId(monitor.id);
|
||||
|
||||
if (
|
||||
checksData.monitorType !== "http" &&
|
||||
checksData.monitorType !== "ping" &&
|
||||
checksData.monitorType !== "docker" &&
|
||||
checksData.monitorType !== "port" &&
|
||||
checksData.monitorType !== "game" &&
|
||||
checksData.monitorType !== "grpc" &&
|
||||
checksData.monitorType !== "websocket" &&
|
||||
checksData.monitorType !== "dns"
|
||||
) {
|
||||
if (!isUptimeChecksResult(checksData)) {
|
||||
throw new AppError({ message: `${monitor.type} monitors are not supported for uptime details`, status: 400 });
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,19 @@ export type MonitorType = (typeof MonitorTypes)[number];
|
||||
export const GeoCheckSupportedTypes: readonly MonitorType[] = ["http", "ping"] as const;
|
||||
export const supportsGeoCheck = (type: MonitorType): boolean => GeoCheckSupportedTypes.includes(type);
|
||||
|
||||
export const UptimeDetailsSupportedTypes = [
|
||||
"http",
|
||||
"ping",
|
||||
"docker",
|
||||
"port",
|
||||
"game",
|
||||
"grpc",
|
||||
"websocket",
|
||||
"dns",
|
||||
] as const satisfies readonly MonitorType[];
|
||||
export type UptimeDetailsSupportedType = (typeof UptimeDetailsSupportedTypes)[number];
|
||||
export const supportsUptimeDetails = (type: MonitorType): type is UptimeDetailsSupportedType => UptimeDetailsSupportedTypes.some((t) => t === type);
|
||||
|
||||
export const MonitorStatuses = ["up", "down", "paused", "initializing", "maintenance", "breached"] as const;
|
||||
export type MonitorStatus = (typeof MonitorStatuses)[number];
|
||||
|
||||
|
||||
@@ -21,9 +21,10 @@ export const dnsServerValidation = z
|
||||
.refine((v) => z.ipv4().safeParse(v).success || z.ipv6().safeParse(v).success, "Enter a valid IPv4 or IPv6 address (e.g. 8.8.8.8)");
|
||||
|
||||
// Hostname (FQDN) — labels of 1-63 alphanumerics/hyphens separated by dots,
|
||||
// no scheme, port, path, or whitespace. Total length ≤ 253. Kept in sync with
|
||||
// optionally prefixed with `_` for service labels (e.g. _dmarc, _imaps._tcp).
|
||||
// No scheme, port, path, or whitespace. Total length ≤ 253. Kept in sync with
|
||||
// the client-side regex in client/src/Validation/monitor.ts.
|
||||
export const dnsHostnameRegex = /^(?=.{1,253}$)([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,63}$/;
|
||||
export const dnsHostnameRegex = /^(?=.{1,253}$)([a-zA-Z0-9_](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,63}$/;
|
||||
|
||||
export const booleanCoercion = z.preprocess((val) => {
|
||||
if (val === "true" || val === true) return true;
|
||||
|
||||
@@ -75,6 +75,18 @@ describe("monitorValidation — DNS fields", () => {
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it("accepts service labels with leading underscore (DMARC, SRV, ACME)", () => {
|
||||
for (const url of ["_dmarc.example.com", "_imaps._tcp.example.com", "_acme-challenge.example.com"]) {
|
||||
const parsed = createMonitorBodyValidation.parse({
|
||||
...baseDnsBody,
|
||||
url,
|
||||
dnsServer: "8.8.8.8",
|
||||
dnsRecordType: "TXT",
|
||||
});
|
||||
expect(parsed.url).toBe(url);
|
||||
}
|
||||
});
|
||||
|
||||
it("does not enforce the hostname rule for non-DNS monitors", () => {
|
||||
const parsed = createMonitorBodyValidation.parse({
|
||||
name: "HTTP check",
|
||||
|
||||
Reference in new issue
Block a user