Merge branch 'develop' into fix/status-page-monitor-leak
This commit is contained in:
commit
436c11bcba
6 files changed
+329
-44
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import RE2 from "re2";
|
||||
import { booleanCoercion, dnsHostnameRegex, dnsServerValidation } from "./shared.js";
|
||||
import { GeoContinents } from "@/domain/geo-checks/geo-check.type.js";
|
||||
import {
|
||||
@@ -71,6 +72,20 @@ const refineStrategyType = (body: { type?: string; strategy?: string }, ctx: z.R
|
||||
}
|
||||
};
|
||||
|
||||
// The regex is executed at check time with RE2, reject patterns RE2 won't accept
|
||||
const refineRegexPattern = (body: { matchMethod?: string; expectedValue?: string }, ctx: z.RefinementCtx) => {
|
||||
if (body.matchMethod !== "regex" || !body.expectedValue) return;
|
||||
try {
|
||||
new RE2(body.expectedValue);
|
||||
} catch {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
path: ["expectedValue"],
|
||||
message: "Invalid regex pattern. Backreferences and lookahead/lookbehind are not supported.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const refineHeadMatching = (body: { method?: string; useAdvancedMatching?: boolean; jsonPath?: string }, ctx: z.RefinementCtx) => {
|
||||
if (body.method === "HEAD" && (body.useAdvancedMatching === true || (body.jsonPath ?? "") !== "")) {
|
||||
ctx.addIssue({
|
||||
@@ -120,7 +135,8 @@ export const createMonitorBodyValidation = z
|
||||
})
|
||||
.superRefine(refineDnsHostname)
|
||||
.superRefine(refineStrategyType)
|
||||
.superRefine(refineHeadMatching);
|
||||
.superRefine(refineHeadMatching)
|
||||
.superRefine(refineRegexPattern);
|
||||
|
||||
export const editMonitorBodyValidation = z
|
||||
.object({
|
||||
@@ -159,7 +175,8 @@ export const editMonitorBodyValidation = z
|
||||
})
|
||||
.superRefine(refineDnsHostname)
|
||||
.superRefine(refineStrategyType)
|
||||
.superRefine(refineHeadMatching);
|
||||
.superRefine(refineHeadMatching)
|
||||
.superRefine(refineRegexPattern);
|
||||
|
||||
export const pauseMonitorParamValidation = z.object({
|
||||
monitorId: z.string().min(1, "Monitor ID is required"),
|
||||
@@ -232,7 +249,8 @@ const importedMonitorSchema = z
|
||||
})
|
||||
.superRefine(refineDnsHostname)
|
||||
.superRefine(refineStrategyType)
|
||||
.superRefine(refineHeadMatching);
|
||||
.superRefine(refineHeadMatching)
|
||||
.superRefine(refineRegexPattern);
|
||||
|
||||
export const importMonitorsBodyValidation = z.object({
|
||||
monitors: z.array(importedMonitorSchema).min(1, "At least one monitor is required"),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Monitor } from "@/domain/monitors/monitor.types.js";
|
||||
import jmespath from "jmespath";
|
||||
import RE2 from "re2";
|
||||
type JmesPath = typeof jmespath;
|
||||
|
||||
export interface IAdvancedMatcher {
|
||||
@@ -9,13 +10,6 @@ export interface IAdvancedMatcher {
|
||||
export class AdvancedMatcher implements IAdvancedMatcher {
|
||||
constructor(private jmespath: JmesPath) {}
|
||||
|
||||
private compare(actual: unknown, expected: string, method?: string): boolean {
|
||||
if (method === "equal") return String(actual) === expected;
|
||||
if (method === "include") return String(actual).includes(expected);
|
||||
if (method === "regex") return new RegExp(expected).test(String(actual));
|
||||
return String(actual) === expected; // Default
|
||||
}
|
||||
|
||||
validate<T>(payload: T, monitor: Monitor): { ok: boolean; message: string; extracted?: T | undefined } {
|
||||
const { useAdvancedMatching, jsonPath, matchMethod, expectedValue } = monitor;
|
||||
if (!useAdvancedMatching) return { ok: true, message: "Success" };
|
||||
@@ -30,19 +24,38 @@ export class AdvancedMatcher implements IAdvancedMatcher {
|
||||
}
|
||||
}
|
||||
|
||||
if (expectedValue) {
|
||||
const ok = this.compare(dataToValidate, expectedValue, matchMethod);
|
||||
if (!expectedValue) {
|
||||
const isFalsy = dataToValidate === false || dataToValidate === "false" || dataToValidate === undefined || dataToValidate === null;
|
||||
return {
|
||||
ok,
|
||||
message: ok ? "Success" : "Expected value did not match",
|
||||
ok: !isFalsy,
|
||||
message: !isFalsy ? "Success" : "Extracted value is falsy",
|
||||
extracted: dataToValidate,
|
||||
};
|
||||
}
|
||||
|
||||
const isFalsy = dataToValidate === false || dataToValidate === "false" || dataToValidate === undefined || dataToValidate === null;
|
||||
let actual: string;
|
||||
try {
|
||||
actual = String(dataToValidate);
|
||||
} catch {
|
||||
return { ok: false, message: "Error evaluating response value", extracted: dataToValidate };
|
||||
}
|
||||
|
||||
let ok: boolean;
|
||||
if (matchMethod === "include") {
|
||||
ok = actual.includes(expectedValue);
|
||||
} else if (matchMethod === "regex") {
|
||||
try {
|
||||
ok = new RE2(expectedValue).test(actual);
|
||||
} catch {
|
||||
return { ok: false, message: "Invalid regex pattern", extracted: dataToValidate };
|
||||
}
|
||||
} else {
|
||||
ok = actual === expectedValue;
|
||||
}
|
||||
|
||||
return {
|
||||
ok: !isFalsy,
|
||||
message: !isFalsy ? "Success" : "Extracted value is falsy",
|
||||
ok,
|
||||
message: ok ? "Success" : "Expected value did not match",
|
||||
extracted: dataToValidate,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user