mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-24 17:52:33 +00:00
Adding guiderails
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { db } from "@server/db";
|
||||
import { domains, orgDomains, domainNamespaces } from "@server/db";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { domains, orgDomains, domainNamespaces, resources } from "@server/db";
|
||||
import { eq, and, like, not } from "drizzle-orm";
|
||||
import { subdomainSchema, wildcardSubdomainSchema } from "@server/lib/schemas";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import config from "./config";
|
||||
|
||||
export type DomainValidationResult =
|
||||
| {
|
||||
@@ -71,7 +72,8 @@ export async function validateAndConstructDomain(
|
||||
const isWildcard =
|
||||
subdomain !== undefined &&
|
||||
subdomain !== null &&
|
||||
subdomain.includes("*");
|
||||
subdomain.includes("*") &&
|
||||
domainRes.domains.type !== "cname";
|
||||
|
||||
// Wildcard subdomains are not allowed on CNAME domains
|
||||
if (isWildcard && domainRes.domains.type === "cname") {
|
||||
@@ -97,6 +99,20 @@ export async function validateAndConstructDomain(
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
isWildcard &&
|
||||
domainRes.domains.type == "wildcard" &&
|
||||
!(
|
||||
domainRes.domains.preferWildcardCert ||
|
||||
config.getRawConfig().traefik.prefer_wildcard_cert
|
||||
)
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Wildcard domains are not supported without configuring certificate resolver for wildcard certs and marking it as prefered."
|
||||
};
|
||||
}
|
||||
|
||||
// Validate wildcard subdomain format
|
||||
if (isWildcard) {
|
||||
const parsedWildcard = wildcardSubdomainSchema.safeParse(subdomain);
|
||||
@@ -125,7 +141,8 @@ export async function validateAndConstructDomain(
|
||||
if (subdomain !== undefined && subdomain !== null) {
|
||||
if (!isWildcard) {
|
||||
// Validate regular subdomain format for wildcard domains
|
||||
const parsedSubdomain = subdomainSchema.safeParse(subdomain);
|
||||
const parsedSubdomain =
|
||||
subdomainSchema.safeParse(subdomain);
|
||||
if (!parsedSubdomain.success) {
|
||||
return {
|
||||
success: false,
|
||||
@@ -160,3 +177,81 @@ export async function validateAndConstructDomain(
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given fullDomain conflicts with any existing wildcard resources,
|
||||
* or (if the fullDomain is itself a wildcard) whether any existing resources would
|
||||
* be matched by it.
|
||||
*
|
||||
* @param fullDomain - The fully-constructed domain to check (may contain a leading `*`)
|
||||
* @param excludeResourceId - Optional resource ID to exclude from the check (for updates)
|
||||
* @returns An object with `conflict: true` and a human-readable `message`, or `conflict: false`
|
||||
*/
|
||||
export async function checkWildcardDomainConflict(
|
||||
fullDomain: string,
|
||||
excludeResourceId?: number
|
||||
): Promise<{ conflict: false } | { conflict: true; message: string }> {
|
||||
const isWildcard = fullDomain.startsWith("*.");
|
||||
|
||||
if (isWildcard) {
|
||||
// e.g. fullDomain = "*.example.com" → suffix = ".example.com"
|
||||
const suffix = fullDomain.slice(1); // ".example.com"
|
||||
|
||||
// Find any existing non-wildcard resource whose fullDomain ends with this suffix
|
||||
// e.g. "test.example.com" or "foo.example.com"
|
||||
const conflicting = await db
|
||||
.select({
|
||||
resourceId: resources.resourceId,
|
||||
fullDomain: resources.fullDomain
|
||||
})
|
||||
.from(resources)
|
||||
.where(like(resources.fullDomain, `%${suffix}`));
|
||||
|
||||
const matches = conflicting.filter(
|
||||
(r) =>
|
||||
!r.fullDomain!.startsWith("*.") &&
|
||||
r.fullDomain!.endsWith(suffix) &&
|
||||
(excludeResourceId === undefined ||
|
||||
r.resourceId !== excludeResourceId)
|
||||
);
|
||||
|
||||
if (matches.length > 0) {
|
||||
return {
|
||||
conflict: true,
|
||||
message: `Wildcard domain ${fullDomain} conflicts with existing resource(s): ${matches.map((r) => r.fullDomain).join(", ")}`
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Specific domain — check if any existing wildcard would match it.
|
||||
// e.g. fullDomain = "test.example.com"
|
||||
// We look for a wildcard "*.example.com" which means fullDomain ends with ".example.com"
|
||||
const dotIndex = fullDomain.indexOf(".");
|
||||
if (dotIndex !== -1) {
|
||||
const suffix = fullDomain.slice(dotIndex); // ".example.com"
|
||||
const wildcardPattern = `*.${fullDomain.slice(dotIndex + 1)}`; // "*.example.com"
|
||||
|
||||
const conflicting = await db
|
||||
.select({
|
||||
resourceId: resources.resourceId,
|
||||
fullDomain: resources.fullDomain
|
||||
})
|
||||
.from(resources)
|
||||
.where(eq(resources.fullDomain, wildcardPattern));
|
||||
|
||||
const matches = conflicting.filter(
|
||||
(r) =>
|
||||
excludeResourceId === undefined ||
|
||||
r.resourceId !== excludeResourceId
|
||||
);
|
||||
|
||||
if (matches.length > 0) {
|
||||
return {
|
||||
conflict: true,
|
||||
message: `Domain ${fullDomain} conflicts with existing wildcard resource(s): ${matches.map((r) => r.fullDomain).join(", ")}`
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { conflict: false };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user