Add domain component to the site resource

This commit is contained in:
Owen
2026-04-11 17:19:18 -07:00
parent 9e50569c31
commit fc4633db91
4 changed files with 95 additions and 18 deletions

View File

@@ -249,7 +249,12 @@ export const siteResources = pgTable("siteResources", {
authDaemonPort: integer("authDaemonPort").default(22123),
authDaemonMode: varchar("authDaemonMode", { length: 32 })
.$type<"site" | "remote">()
.default("site")
.default("site"),
domainId: varchar("domainId").references(() => domains.domainId, {
onDelete: "set null"
}),
subdomain: varchar("subdomain"),
fullDomain: varchar("fullDomain")
});
export const clientSiteResources = pgTable("clientSiteResources", {

View File

@@ -277,7 +277,12 @@ export const siteResources = sqliteTable("siteResources", {
authDaemonPort: integer("authDaemonPort").default(22123),
authDaemonMode: text("authDaemonMode")
.$type<"site" | "remote">()
.default("site")
.default("site"),
domainId: text("domainId").references(() => domains.domainId, {
onDelete: "set null"
}),
subdomain: text("subdomain"),
fullDomain: text("fullDomain"),
});
export const clientSiteResources = sqliteTable("clientSiteResources", {

View File

@@ -28,6 +28,7 @@ import { NextFunction, Request, Response } from "express";
import createHttpError from "http-errors";
import { z } from "zod";
import { fromError } from "zod-validation-error";
import { validateAndConstructDomain } from "@server/lib/domainUtils";
const createSiteResourceParamsSchema = z.strictObject({
orgId: z.string()
@@ -58,15 +59,14 @@ const createSiteResourceSchema = z
udpPortRangeString: portRangeStringSchema,
disableIcmp: z.boolean().optional(),
authDaemonPort: z.int().positive().optional(),
authDaemonMode: z.enum(["site", "remote"]).optional()
authDaemonMode: z.enum(["site", "remote"]).optional(),
domainId: z.string().optional(), // only used for http mode, we need this to verify the alias is unique within the org
subdomain: z.string().optional() // only used for http mode, we need this to verify the alias is unique within the org
})
.strict()
.refine(
(data) => {
if (
data.mode === "host" ||
data.mode == "http"
) {
if (data.mode === "host" || data.mode == "http") {
if (data.mode == "host") {
// Check if it's a valid IP address using zod (v4 or v6)
const isValidIP = z
@@ -196,7 +196,9 @@ export async function createSiteResource(
udpPortRangeString,
disableIcmp,
authDaemonPort,
authDaemonMode
authDaemonMode,
domainId,
subdomain
} = parsedBody.data;
// Verify the site exists and belongs to the org
@@ -248,15 +250,47 @@ export async function createSiteResource(
);
}
if (domainId && alias) {
// throw an error because we can only have one or the other
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Alias and domain cannot both be set. Please choose one or the other."
)
);
}
let fullDomain: string | null = null;
let finalSubdomain: string | null = null;
let finalAlias = alias ? alias.trim() : null;
if (domainId && subdomain) {
// Validate domain and construct full domain
const domainResult = await validateAndConstructDomain(
domainId,
orgId,
subdomain
);
if (!domainResult.success) {
return next(
createHttpError(HttpCode.BAD_REQUEST, domainResult.error)
);
}
fullDomain = domainResult.fullDomain;
finalSubdomain = domainResult.subdomain;
finalAlias = fullDomain; // we will use the full domain as the alias for uniqueness checks and routing
}
// make sure the alias is unique within the org if provided
if (alias) {
if (finalAlias) {
const [conflict] = await db
.select()
.from(siteResources)
.where(
and(
eq(siteResources.orgId, orgId),
eq(siteResources.alias, alias.trim())
eq(siteResources.alias, finalAlias.trim())
)
)
.limit(1);
@@ -296,11 +330,13 @@ export async function createSiteResource(
scheme,
destinationPort,
enabled,
alias,
alias: finalAlias,
aliasAddress,
tcpPortRangeString,
udpPortRangeString,
disableIcmp
disableIcmp,
domainId,
subdomain: finalSubdomain
};
if (isLicensedSshPam) {
if (authDaemonPort !== undefined)

View File

@@ -14,6 +14,7 @@ import {
userSiteResources
} from "@server/db";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
import { validateAndConstructDomain } from "@server/lib/domainUtils";
import {
generateAliasConfig,
generateRemoteSubnets,
@@ -72,7 +73,9 @@ const updateSiteResourceSchema = z
udpPortRangeString: portRangeStringSchema,
disableIcmp: z.boolean().optional(),
authDaemonPort: z.int().positive().nullish(),
authDaemonMode: z.enum(["site", "remote"]).optional()
authDaemonMode: z.enum(["site", "remote"]).optional(),
domainId: z.string().optional(),
subdomain: z.string().optional()
})
.strict()
.refine(
@@ -212,7 +215,9 @@ export async function updateSiteResource(
udpPortRangeString,
disableIcmp,
authDaemonPort,
authDaemonMode
authDaemonMode,
domainId,
subdomain
} = parsedBody.data;
const [site] = await db
@@ -302,15 +307,37 @@ export async function updateSiteResource(
}
}
let fullDomain: string | null = null;
let finalSubdomain: string | null = null;
let finalAlias = alias ? alias.trim() : null;
if (domainId && subdomain) {
// Validate domain and construct full domain
const domainResult = await validateAndConstructDomain(
domainId,
org.orgId,
subdomain
);
if (!domainResult.success) {
return next(
createHttpError(HttpCode.BAD_REQUEST, domainResult.error)
);
}
fullDomain = domainResult.fullDomain;
finalSubdomain = domainResult.subdomain;
finalAlias = fullDomain; // we will use the full domain as the alias for uniqueness checks and routing
}
// make sure the alias is unique within the org if provided
if (alias) {
if (finalAlias) {
const [conflict] = await db
.select()
.from(siteResources)
.where(
and(
eq(siteResources.orgId, existingSiteResource.orgId),
eq(siteResources.alias, alias.trim()),
eq(siteResources.alias, finalAlias.trim()),
ne(siteResources.siteResourceId, siteResourceId) // exclude self
)
)
@@ -378,10 +405,12 @@ export async function updateSiteResource(
destination,
destinationPort,
enabled,
alias: alias && alias.trim() ? alias : null,
alias: finalAlias,
tcpPortRangeString,
udpPortRangeString,
disableIcmp,
domainId,
subdomain: finalSubdomain,
...sshPamSet
})
.where(
@@ -484,10 +513,12 @@ export async function updateSiteResource(
destination: destination,
destinationPort: destinationPort,
enabled: enabled,
alias: alias && alias.trim() ? alias : null,
alias: finalAlias,
tcpPortRangeString: tcpPortRangeString,
udpPortRangeString: udpPortRangeString,
disableIcmp: disableIcmp,
domainId,
subdomain: finalSubdomain,
...sshPamSet
})
.where(