mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-15 19:05:28 +02:00
* fix(domain): validate hostname format to reject invalid characters Underscores and other invalid characters were accepted in domain inputs with no validation, causing Let's Encrypt to silently fail certificate issuance while Dokploy fell back to a self-signed cert. Fixes #4716 * fix(create-server): update SSH key label for clarity in server creation form
139 lines
3.8 KiB
TypeScript
139 lines
3.8 KiB
TypeScript
import { z } from "zod";
|
|
import {
|
|
INVALID_HOSTNAME_MESSAGE,
|
|
VALID_HOSTNAME_REGEX,
|
|
} from "../../utils/hostname-validation";
|
|
|
|
export const domain = z
|
|
.object({
|
|
host: z
|
|
.string()
|
|
.min(1, { message: "Add a hostname" })
|
|
.refine((val) => val === val.trim(), {
|
|
message: "Domain name cannot have leading or trailing spaces",
|
|
})
|
|
.transform((val) => val.trim())
|
|
.refine((val) => VALID_HOSTNAME_REGEX.test(val), {
|
|
message: INVALID_HOSTNAME_MESSAGE,
|
|
}),
|
|
path: z.string().min(1).optional(),
|
|
internalPath: z.string().optional(),
|
|
stripPath: z.boolean().optional(),
|
|
port: z
|
|
.number()
|
|
.min(1, { message: "Port must be at least 1" })
|
|
.max(65535, { message: "Port must be 65535 or below" })
|
|
.optional(),
|
|
https: z.boolean().optional(),
|
|
certificateType: z.enum(["letsencrypt", "none", "custom"]).optional(),
|
|
customCertResolver: z.string(),
|
|
middlewares: z.array(z.string()).optional(),
|
|
})
|
|
.superRefine((input, ctx) => {
|
|
if (input.https && !input.certificateType) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["certificateType"],
|
|
message: "Required",
|
|
});
|
|
}
|
|
|
|
if (input.certificateType === "custom" && !input.customCertResolver) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["customCertResolver"],
|
|
message: "Required when certificate type is custom",
|
|
});
|
|
}
|
|
|
|
// Validate stripPath requires a valid path
|
|
if (input.stripPath && (!input.path || input.path === "/")) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["stripPath"],
|
|
message:
|
|
"Strip path can only be enabled when a path other than '/' is specified",
|
|
});
|
|
}
|
|
|
|
// Validate internalPath starts with /
|
|
if (
|
|
input.internalPath &&
|
|
input.internalPath !== "/" &&
|
|
!input.internalPath.startsWith("/")
|
|
) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["internalPath"],
|
|
message: "Internal path must start with '/'",
|
|
});
|
|
}
|
|
});
|
|
|
|
export const domainCompose = z
|
|
.object({
|
|
host: z
|
|
.string()
|
|
.min(1, { message: "Add a hostname" })
|
|
.refine((val) => val === val.trim(), {
|
|
message: "Domain name cannot have leading or trailing spaces",
|
|
})
|
|
.transform((val) => val.trim())
|
|
.refine((val) => VALID_HOSTNAME_REGEX.test(val), {
|
|
message: INVALID_HOSTNAME_MESSAGE,
|
|
}),
|
|
path: z.string().min(1).optional(),
|
|
internalPath: z.string().optional(),
|
|
stripPath: z.boolean().optional(),
|
|
port: z
|
|
.number()
|
|
.min(1, { message: "Port must be at least 1" })
|
|
.max(65535, { message: "Port must be 65535 or below" })
|
|
.optional(),
|
|
https: z.boolean().optional(),
|
|
certificateType: z.enum(["letsencrypt", "none", "custom"]).optional(),
|
|
customCertResolver: z.string(),
|
|
serviceName: z.string().min(1, { message: "Service name is required" }),
|
|
middlewares: z.array(z.string()).optional(),
|
|
})
|
|
.superRefine((input, ctx) => {
|
|
if (input.https && !input.certificateType) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["certificateType"],
|
|
message: "Required",
|
|
});
|
|
}
|
|
|
|
if (input.certificateType === "custom" && !input.customCertResolver) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["customCertResolver"],
|
|
message: "Required when certificate type is custom",
|
|
});
|
|
}
|
|
|
|
// Validate stripPath requires a valid path
|
|
if (input.stripPath && (!input.path || input.path === "/")) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["stripPath"],
|
|
message:
|
|
"Strip path can only be enabled when a path other than '/' is specified",
|
|
});
|
|
}
|
|
|
|
// Validate internalPath starts with /
|
|
if (
|
|
input.internalPath &&
|
|
input.internalPath !== "/" &&
|
|
!input.internalPath.startsWith("/")
|
|
) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["internalPath"],
|
|
message: "Internal path must start with '/'",
|
|
});
|
|
}
|
|
});
|