feat(domains): add internal path routing and

strip path functionality to compose

  - Add internalPath field to route requests
  to different paths internally
  - Add stripPath option to remove external
  path prefix before forwarding
  - Improves validation for stripPath (requires
  non-root path) and internalPath (must start
  with /)
This commit is contained in:
Jhonatan Caldeira
2025-06-22 14:55:27 -03:00
parent df8f1252a0
commit fd0f679d0f
3 changed files with 80 additions and 0 deletions

View File

@@ -86,6 +86,24 @@ export const domain = z
message: "Required",
});
}
// 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 '/'",
});
}
});
type Domain = z.infer<typeof domain>;

View File

@@ -4,6 +4,8 @@ export const domain = z
.object({
host: z.string().min(1, { message: "Add a hostname" }),
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" })
@@ -29,12 +31,32 @@ export const domain = z
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: "Host is required" }),
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" })
@@ -61,4 +83,22 @@ export const domainCompose = z
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 '/'",
});
}
});

View File

@@ -301,6 +301,8 @@ export const createDomainLabels = (
certificateType,
path,
customCertResolver,
stripPath,
internalPath
} = domain;
const routerName = `${appName}-${uniqueConfigKey}-${entrypoint}`;
const labels = [
@@ -310,6 +312,26 @@ export const createDomainLabels = (
`traefik.http.routers.${routerName}.service=${routerName}`,
];
// Validate stripPath - it should only be used when path is defined and not "/"
if (stripPath) {
if (!path || path === "/") {
console.warn(`stripPath is enabled but path is not defined or is "/" for domain ${host}`);
} else {
const middlewareName = `stripprefix-${appName}-${uniqueConfigKey}`;
labels.push(`traefik.http.middlewares.${middlewareName}.stripprefix.prefixes=${path}`);
}
}
// Validate internalPath - ensure it's a valid path format
if (internalPath && internalPath !== "/") {
if (!internalPath.startsWith("/")) {
console.warn(`internalPath "${internalPath}" should start with "/" and not be empty for domain ${host}`);
} else {
const middlewareName = `addprefix-${appName}-${uniqueConfigKey}`;
labels.push(`traefik.http.middlewares.${middlewareName}.addprefix.prefix=${internalPath}`);
}
}
if (entrypoint === "web" && https) {
labels.push(
`traefik.http.routers.${routerName}.middlewares=redirect-to-https@file`,