From 464d4990dfac13354ba3e39dee2997757423057d Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 27 May 2026 11:34:34 -0700 Subject: [PATCH] Fix cascading errors --- server/db/pg/schema/schema.ts | 2 +- server/db/sqlite/schema/schema.ts | 2 +- server/lib/ip.ts | 14 ++++---- server/lib/rebuildClientAssociations.ts | 6 ++++ .../private/lib/traefik/getTraefikConfig.ts | 2 +- server/private/routers/ssh/signSshKey.ts | 2 +- .../siteResource/createSiteResource.ts | 13 +++++--- .../siteResource/updateSiteResource.ts | 4 +++ src/app/rdp/page.tsx | 2 +- src/components/ClientResourcesTable.tsx | 2 +- .../CreateInternalResourceDialog.tsx | 2 +- src/components/EditInternalResourceDialog.tsx | 2 +- src/components/InternalResourceForm.tsx | 33 +++++++++++++++---- src/components/SiteResourcesOverview.tsx | 3 +- src/lib/formatSiteResourceAccess.ts | 7 ++-- 15 files changed, 68 insertions(+), 28 deletions(-) diff --git a/server/db/pg/schema/schema.ts b/server/db/pg/schema/schema.ts index 770e891fb..06f204eff 100644 --- a/server/db/pg/schema/schema.ts +++ b/server/db/pg/schema/schema.ts @@ -350,7 +350,7 @@ export const siteResources = pgTable("siteResources", { scheme: varchar("scheme").$type<"http" | "https">(), // only for when we are doing https or http mode proxyPort: integer("proxyPort"), // only for port mode destinationPort: integer("destinationPort"), // only for port mode - destination: varchar("destination").notNull(), // ip, cidr, hostname; validate against the mode + destination: varchar("destination"), // ip, cidr, hostname; validate against the mode enabled: boolean("enabled").notNull().default(true), alias: varchar("alias"), aliasAddress: varchar("aliasAddress"), diff --git a/server/db/sqlite/schema/schema.ts b/server/db/sqlite/schema/schema.ts index c36719130..6455b0599 100644 --- a/server/db/sqlite/schema/schema.ts +++ b/server/db/sqlite/schema/schema.ts @@ -384,7 +384,7 @@ export const siteResources = sqliteTable("siteResources", { scheme: text("scheme").$type<"http" | "https">(), // only for when we are doing https or http mode proxyPort: integer("proxyPort"), // only for port mode destinationPort: integer("destinationPort"), // only for port mode - destination: text("destination").notNull(), // ip, cidr, hostname + destination: text("destination"), // ip, cidr, hostname enabled: integer("enabled", { mode: "boolean" }).notNull().default(true), alias: text("alias"), aliasAddress: text("aliasAddress"), diff --git a/server/lib/ip.ts b/server/lib/ip.ts index 9989b978f..e80f6baf8 100644 --- a/server/lib/ip.ts +++ b/server/lib/ip.ts @@ -475,6 +475,8 @@ export function generateRemoteSubnets( ): string[] { const remoteSubnets = allSiteResources .filter((sr) => { + if (!sr.destination) return false; + if (sr.mode === "cidr") { // check if its a valid CIDR using zod const cidrSchema = z.union([z.cidrv4(), z.cidrv6()]); @@ -496,7 +498,7 @@ export function generateRemoteSubnets( } return ""; // This should never be reached due to filtering, but satisfies TypeScript }) - .filter((subnet) => subnet !== ""); // Remove empty strings just to be safe + .filter((subnet): subnet is string => subnet !== "" && subnet !== null); // Remove invalid values just to be safe // remove duplicates return Array.from(new Set(remoteSubnets)); } @@ -581,7 +583,7 @@ export function generateSubnetProxyTargets( targets.push({ sourcePrefix: clientPrefix, destPrefix: `${siteResource.aliasAddress}/32`, - rewriteTo: destination, + rewriteTo: destination!, portRange, disableIcmp }); @@ -589,7 +591,7 @@ export function generateSubnetProxyTargets( } else if (siteResource.mode == "cidr") { targets.push({ sourcePrefix: clientPrefix, - destPrefix: siteResource.destination, + destPrefix: siteResource.destination!, portRange, disableIcmp }); @@ -671,7 +673,7 @@ export async function generateSubnetProxyTargetV2( targets.push({ sourcePrefixes: [], destPrefix: `${siteResource.aliasAddress}/32`, - rewriteTo: destination, + rewriteTo: destination!, portRange, disableIcmp, resourceId: siteResource.siteResourceId @@ -680,7 +682,7 @@ export async function generateSubnetProxyTargetV2( } else if (siteResource.mode == "cidr") { targets.push({ sourcePrefixes: [], - destPrefix: siteResource.destination, + destPrefix: siteResource.destination!, portRange, disableIcmp, resourceId: siteResource.siteResourceId @@ -738,7 +740,7 @@ export async function generateSubnetProxyTargetV2( protocol: siteResource.ssl ? "https" : "http", httpTargets: [ { - destAddr: siteResource.destination, + destAddr: siteResource.destination!, destPort: siteResource.destinationPort, scheme: siteResource.scheme } diff --git a/server/lib/rebuildClientAssociations.ts b/server/lib/rebuildClientAssociations.ts index a7a134f6d..7dc28750b 100644 --- a/server/lib/rebuildClientAssociations.ts +++ b/server/lib/rebuildClientAssociations.ts @@ -823,6 +823,9 @@ async function handleSubnetProxyTargetUpdates( } for (const client of removedClients) { + if (!siteResource.destination) { + continue; + } // Check if this client still has access to another resource // on this specific site with the same destination. We scope // by siteId (via siteNetworks) rather than networkId because @@ -1457,6 +1460,9 @@ async function handleMessagesForClientResources( } try { + if (!resource.destination) { + continue; + } // Check if this client still has access to another resource // on this specific site with the same destination. We scope // by siteId (via siteNetworks) rather than networkId because diff --git a/server/private/lib/traefik/getTraefikConfig.ts b/server/private/lib/traefik/getTraefikConfig.ts index 132bb95bc..b9890582c 100644 --- a/server/private/lib/traefik/getTraefikConfig.ts +++ b/server/private/lib/traefik/getTraefikConfig.ts @@ -390,7 +390,7 @@ export async function getTraefikConfig( let siteResourcesWithFullDomain: { siteResourceId: number; fullDomain: string | null; - mode: "http" | "host" | "cidr"; + mode: "http" | "host" | "cidr" | "ssh"; }[] = []; if (build == "enterprise") { // we dont want to do this on the cloud diff --git a/server/private/routers/ssh/signSshKey.ts b/server/private/routers/ssh/signSshKey.ts index 61232b51f..8e772118e 100644 --- a/server/private/routers/ssh/signSshKey.ts +++ b/server/private/routers/ssh/signSshKey.ts @@ -546,7 +546,7 @@ export async function signSshKey( if (resource.alias && resource.alias != "") { sshHost = resource.alias; } else { - sshHost = resource.destination; + sshHost = resource.destination || ""; // TODO: IF WE HAVE THE NATIVE SSH MODE WHAT SHOULD WE DO HERE? } } else if (resource.authDaemonMode === "native") { if (siteIds.length > 1) { diff --git a/server/routers/siteResource/createSiteResource.ts b/server/routers/siteResource/createSiteResource.ts index ee7bc5ad1..a6f1b352f 100644 --- a/server/routers/siteResource/createSiteResource.ts +++ b/server/routers/siteResource/createSiteResource.ts @@ -51,7 +51,7 @@ const createSiteResourceSchema = z siteId: z.number().int().positive().optional(), // DEPRECATED: for backward compatibility, we will convert this to siteIds array if provided // proxyPort: z.int().positive().optional(), destinationPort: z.int().positive().optional(), - destination: z.string().min(1), + destination: z.string().min(1).optional(), enabled: z.boolean().default(true), alias: z .string() @@ -75,7 +75,10 @@ const createSiteResourceSchema = z .strict() .refine( (data) => { - if (data.mode === "host" || data.mode === "ssh") { + if ( + (data.mode === "host" || data.mode === "ssh") && + data.destination + ) { // Check if it's a valid IP address using zod (v4 or v6) const isValidIP = z // .union([z.ipv4(), z.ipv6()]) @@ -289,8 +292,8 @@ export async function createSiteResource( .safeParse(destination).success; if ( isIp && - (isIpInCidr(destination, org.subnet) || - isIpInCidr(destination, org.utilitySubnet)) + (isIpInCidr(destination!, org.subnet) || + isIpInCidr(destination!, org.utilitySubnet)) ) { return next( createHttpError( @@ -419,7 +422,7 @@ export async function createSiteResource( mode, ssl, networkId: network.networkId, - destination, + destination: destination, // the ssh can be null scheme, destinationPort, enabled, diff --git a/server/routers/siteResource/updateSiteResource.ts b/server/routers/siteResource/updateSiteResource.ts index 4f827d904..f6513b738 100644 --- a/server/routers/siteResource/updateSiteResource.ts +++ b/server/routers/siteResource/updateSiteResource.ts @@ -866,6 +866,10 @@ export async function handleMessagingForUpdatedSiteResource( for (const client of mergedAllClients) { // does this client have access to another resource on this site that has the same destination still? if so we dont want to remove it from their olm yet // todo: optimize this query if needed + if (!existingSiteResource.destination) { + continue; + } + const oldDestinationStillInUseSites = await trx .select() .from(siteResources) diff --git a/src/app/rdp/page.tsx b/src/app/rdp/page.tsx index d68a2ee89..291a35a8c 100644 --- a/src/app/rdp/page.tsx +++ b/src/app/rdp/page.tsx @@ -15,7 +15,7 @@ export default async function RdpPage() { const host = headersList.get("host") || ""; const hostname = host.split(":")[0]; - let target: { ip: string; port: number; authToken: string } | null = null; + let target: GetBrowserTargetResponse | null = null; let error: string | null = null; try { diff --git a/src/components/ClientResourcesTable.tsx b/src/components/ClientResourcesTable.tsx index a4232e98e..2553bcb39 100644 --- a/src/components/ClientResourcesTable.tsx +++ b/src/components/ClientResourcesTable.tsx @@ -82,7 +82,7 @@ export type InternalResourceRow = { ssl: boolean; // protocol: string | null; // proxyPort: number | null; - destination: string; + destination: string | null; destinationPort: number | null; alias: string | null; aliasAddress: string | null; diff --git a/src/components/CreateInternalResourceDialog.tsx b/src/components/CreateInternalResourceDialog.tsx index 6e6f1abf4..79b28cdb7 100644 --- a/src/components/CreateInternalResourceDialog.tsx +++ b/src/components/CreateInternalResourceDialog.tsx @@ -55,7 +55,7 @@ export default function CreateInternalResourceDialog({ const currentAlias = data.alias?.trim() || ""; if (!currentAlias) { let aliasValue = data.destination; - if (data.destination.toLowerCase() === "localhost") { + if (data.destination?.toLowerCase() === "localhost") { aliasValue = `${cleanForFQDN(data.name)}.internal`; } data = { ...data, alias: aliasValue }; diff --git a/src/components/EditInternalResourceDialog.tsx b/src/components/EditInternalResourceDialog.tsx index 038a163db..297432f25 100644 --- a/src/components/EditInternalResourceDialog.tsx +++ b/src/components/EditInternalResourceDialog.tsx @@ -59,7 +59,7 @@ export default function EditInternalResourceDialog({ const currentAlias = data.alias?.trim() || ""; if (!currentAlias) { let aliasValue = data.destination; - if (data.destination.toLowerCase() === "localhost") { + if (data.destination?.toLowerCase() === "localhost") { aliasValue = `${cleanForFQDN(data.name)}.internal`; } data = { ...data, alias: aliasValue }; diff --git a/src/components/InternalResourceForm.tsx b/src/components/InternalResourceForm.tsx index 41008a2e8..ccabe1506 100644 --- a/src/components/InternalResourceForm.tsx +++ b/src/components/InternalResourceForm.tsx @@ -124,8 +124,8 @@ export const getPortStringFromMode = ( return customValue; }; -export const isHostname = (destination: string): boolean => - /[a-zA-Z]/.test(destination); +export const isHostname = (destination: string | null): boolean => + !!destination && /[a-zA-Z]/.test(destination); export const cleanForFQDN = (name: string): string => name @@ -147,7 +147,7 @@ export type InternalResourceData = { mode: InternalResourceMode; siteIds: number[]; niceId: string; - destination: string; + destination: string | null; alias?: string | null; tcpPortRangeString?: string | null; udpPortRangeString?: string | null; @@ -179,7 +179,7 @@ export type InternalResourceFormValues = { name: string; siteIds: number[]; mode: InternalResourceMode; - destination: string; + destination: string | null; alias?: string | null; niceId?: string; tcpPortRangeString?: string | null; @@ -309,7 +309,7 @@ export function InternalResourceForm({ name: z.string().min(1, t(nameRequiredKey)).max(255, t(nameMaxKey)), siteIds: siteIdsSchema, mode: z.enum(["host", "cidr", "http", "ssh"]), - destination: z.string(), + destination: z.string().nullish(), alias: z.string().nullish(), destinationPort: z .number() @@ -352,9 +352,10 @@ export function InternalResourceForm({ .superRefine((data, ctx) => { const isNativeSsh = data.mode === "ssh" && data.authDaemonMode === "native"; + const trimmedDestination = data.destination?.trim(); if ( !isNativeSsh && - (!data.destination || data.destination.length < 1) + (!trimmedDestination || trimmedDestination.length < 1) ) { ctx.addIssue({ code: z.ZodIssueCode.custom, @@ -735,9 +736,14 @@ export function InternalResourceForm({
{ const siteIds = values.siteIds; + const trimmedDestination = values.destination?.trim(); onSubmit({ ...values, siteIds, + destination: + trimmedDestination && trimmedDestination.length > 0 + ? trimmedDestination + : null, clients: (values.clients ?? []).map((c) => ({ id: c.clientId.toString(), text: c.name @@ -1097,10 +1103,25 @@ export function InternalResourceForm({ + field.onChange( + e.target + .value === + "" + ? null + : e + .target + .value + ) + } /> diff --git a/src/components/SiteResourcesOverview.tsx b/src/components/SiteResourcesOverview.tsx index caa69b7b4..886765284 100644 --- a/src/components/SiteResourcesOverview.tsx +++ b/src/components/SiteResourcesOverview.tsx @@ -68,7 +68,8 @@ function PrivateResourceMeta({ row }: { row: SiteResourceRow }) { const modeLabel: Record = { host: t("editInternalResourceDialogModeHost"), cidr: t("editInternalResourceDialogModeCidr"), - http: t("editInternalResourceDialogModeHttp") + http: t("editInternalResourceDialogModeHttp"), + ssh: t("editInternalResourceDialogModeSsh") }; const dest = formatSiteResourceDestinationDisplay({ mode: row.mode, diff --git a/src/lib/formatSiteResourceAccess.ts b/src/lib/formatSiteResourceAccess.ts index 45e774a3e..82d2bdeef 100644 --- a/src/lib/formatSiteResourceAccess.ts +++ b/src/lib/formatSiteResourceAccess.ts @@ -1,6 +1,6 @@ export type SiteResourceDestinationInput = { - mode: "host" | "cidr" | "http"; - destination: string; + mode: "host" | "cidr" | "http" | "ssh"; + destination: string | null; destinationPort: number | null; scheme: "http" | "https" | null; }; @@ -18,6 +18,9 @@ export function resolveHttpHttpsDisplayPort( export function formatSiteResourceDestinationDisplay( row: SiteResourceDestinationInput ): string { + if (!row.destination) { + return ""; + } const { mode, destination, destinationPort, scheme } = row; if (mode !== "http") { return destination;