Fix cascading errors

This commit is contained in:
Owen
2026-05-27 11:34:34 -07:00
parent cb90672573
commit 464d4990df
15 changed files with 68 additions and 28 deletions

View File

@@ -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 scheme: varchar("scheme").$type<"http" | "https">(), // only for when we are doing https or http mode
proxyPort: integer("proxyPort"), // only for port mode proxyPort: integer("proxyPort"), // only for port mode
destinationPort: integer("destinationPort"), // 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), enabled: boolean("enabled").notNull().default(true),
alias: varchar("alias"), alias: varchar("alias"),
aliasAddress: varchar("aliasAddress"), aliasAddress: varchar("aliasAddress"),

View File

@@ -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 scheme: text("scheme").$type<"http" | "https">(), // only for when we are doing https or http mode
proxyPort: integer("proxyPort"), // only for port mode proxyPort: integer("proxyPort"), // only for port mode
destinationPort: integer("destinationPort"), // 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), enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
alias: text("alias"), alias: text("alias"),
aliasAddress: text("aliasAddress"), aliasAddress: text("aliasAddress"),

View File

@@ -475,6 +475,8 @@ export function generateRemoteSubnets(
): string[] { ): string[] {
const remoteSubnets = allSiteResources const remoteSubnets = allSiteResources
.filter((sr) => { .filter((sr) => {
if (!sr.destination) return false;
if (sr.mode === "cidr") { if (sr.mode === "cidr") {
// check if its a valid CIDR using zod // check if its a valid CIDR using zod
const cidrSchema = z.union([z.cidrv4(), z.cidrv6()]); 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 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 // remove duplicates
return Array.from(new Set(remoteSubnets)); return Array.from(new Set(remoteSubnets));
} }
@@ -581,7 +583,7 @@ export function generateSubnetProxyTargets(
targets.push({ targets.push({
sourcePrefix: clientPrefix, sourcePrefix: clientPrefix,
destPrefix: `${siteResource.aliasAddress}/32`, destPrefix: `${siteResource.aliasAddress}/32`,
rewriteTo: destination, rewriteTo: destination!,
portRange, portRange,
disableIcmp disableIcmp
}); });
@@ -589,7 +591,7 @@ export function generateSubnetProxyTargets(
} else if (siteResource.mode == "cidr") { } else if (siteResource.mode == "cidr") {
targets.push({ targets.push({
sourcePrefix: clientPrefix, sourcePrefix: clientPrefix,
destPrefix: siteResource.destination, destPrefix: siteResource.destination!,
portRange, portRange,
disableIcmp disableIcmp
}); });
@@ -671,7 +673,7 @@ export async function generateSubnetProxyTargetV2(
targets.push({ targets.push({
sourcePrefixes: [], sourcePrefixes: [],
destPrefix: `${siteResource.aliasAddress}/32`, destPrefix: `${siteResource.aliasAddress}/32`,
rewriteTo: destination, rewriteTo: destination!,
portRange, portRange,
disableIcmp, disableIcmp,
resourceId: siteResource.siteResourceId resourceId: siteResource.siteResourceId
@@ -680,7 +682,7 @@ export async function generateSubnetProxyTargetV2(
} else if (siteResource.mode == "cidr") { } else if (siteResource.mode == "cidr") {
targets.push({ targets.push({
sourcePrefixes: [], sourcePrefixes: [],
destPrefix: siteResource.destination, destPrefix: siteResource.destination!,
portRange, portRange,
disableIcmp, disableIcmp,
resourceId: siteResource.siteResourceId resourceId: siteResource.siteResourceId
@@ -738,7 +740,7 @@ export async function generateSubnetProxyTargetV2(
protocol: siteResource.ssl ? "https" : "http", protocol: siteResource.ssl ? "https" : "http",
httpTargets: [ httpTargets: [
{ {
destAddr: siteResource.destination, destAddr: siteResource.destination!,
destPort: siteResource.destinationPort, destPort: siteResource.destinationPort,
scheme: siteResource.scheme scheme: siteResource.scheme
} }

View File

@@ -823,6 +823,9 @@ async function handleSubnetProxyTargetUpdates(
} }
for (const client of removedClients) { for (const client of removedClients) {
if (!siteResource.destination) {
continue;
}
// Check if this client still has access to another resource // Check if this client still has access to another resource
// on this specific site with the same destination. We scope // on this specific site with the same destination. We scope
// by siteId (via siteNetworks) rather than networkId because // by siteId (via siteNetworks) rather than networkId because
@@ -1457,6 +1460,9 @@ async function handleMessagesForClientResources(
} }
try { try {
if (!resource.destination) {
continue;
}
// Check if this client still has access to another resource // Check if this client still has access to another resource
// on this specific site with the same destination. We scope // on this specific site with the same destination. We scope
// by siteId (via siteNetworks) rather than networkId because // by siteId (via siteNetworks) rather than networkId because

View File

@@ -390,7 +390,7 @@ export async function getTraefikConfig(
let siteResourcesWithFullDomain: { let siteResourcesWithFullDomain: {
siteResourceId: number; siteResourceId: number;
fullDomain: string | null; fullDomain: string | null;
mode: "http" | "host" | "cidr"; mode: "http" | "host" | "cidr" | "ssh";
}[] = []; }[] = [];
if (build == "enterprise") { if (build == "enterprise") {
// we dont want to do this on the cloud // we dont want to do this on the cloud

View File

@@ -546,7 +546,7 @@ export async function signSshKey(
if (resource.alias && resource.alias != "") { if (resource.alias && resource.alias != "") {
sshHost = resource.alias; sshHost = resource.alias;
} else { } 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") { } else if (resource.authDaemonMode === "native") {
if (siteIds.length > 1) { if (siteIds.length > 1) {

View File

@@ -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 siteId: z.number().int().positive().optional(), // DEPRECATED: for backward compatibility, we will convert this to siteIds array if provided
// proxyPort: z.int().positive().optional(), // proxyPort: z.int().positive().optional(),
destinationPort: 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), enabled: z.boolean().default(true),
alias: z alias: z
.string() .string()
@@ -75,7 +75,10 @@ const createSiteResourceSchema = z
.strict() .strict()
.refine( .refine(
(data) => { (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) // Check if it's a valid IP address using zod (v4 or v6)
const isValidIP = z const isValidIP = z
// .union([z.ipv4(), z.ipv6()]) // .union([z.ipv4(), z.ipv6()])
@@ -289,8 +292,8 @@ export async function createSiteResource(
.safeParse(destination).success; .safeParse(destination).success;
if ( if (
isIp && isIp &&
(isIpInCidr(destination, org.subnet) || (isIpInCidr(destination!, org.subnet) ||
isIpInCidr(destination, org.utilitySubnet)) isIpInCidr(destination!, org.utilitySubnet))
) { ) {
return next( return next(
createHttpError( createHttpError(
@@ -419,7 +422,7 @@ export async function createSiteResource(
mode, mode,
ssl, ssl,
networkId: network.networkId, networkId: network.networkId,
destination, destination: destination, // the ssh can be null
scheme, scheme,
destinationPort, destinationPort,
enabled, enabled,

View File

@@ -866,6 +866,10 @@ export async function handleMessagingForUpdatedSiteResource(
for (const client of mergedAllClients) { 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 // 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 // todo: optimize this query if needed
if (!existingSiteResource.destination) {
continue;
}
const oldDestinationStillInUseSites = await trx const oldDestinationStillInUseSites = await trx
.select() .select()
.from(siteResources) .from(siteResources)

View File

@@ -15,7 +15,7 @@ export default async function RdpPage() {
const host = headersList.get("host") || ""; const host = headersList.get("host") || "";
const hostname = host.split(":")[0]; 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; let error: string | null = null;
try { try {

View File

@@ -82,7 +82,7 @@ export type InternalResourceRow = {
ssl: boolean; ssl: boolean;
// protocol: string | null; // protocol: string | null;
// proxyPort: number | null; // proxyPort: number | null;
destination: string; destination: string | null;
destinationPort: number | null; destinationPort: number | null;
alias: string | null; alias: string | null;
aliasAddress: string | null; aliasAddress: string | null;

View File

@@ -55,7 +55,7 @@ export default function CreateInternalResourceDialog({
const currentAlias = data.alias?.trim() || ""; const currentAlias = data.alias?.trim() || "";
if (!currentAlias) { if (!currentAlias) {
let aliasValue = data.destination; let aliasValue = data.destination;
if (data.destination.toLowerCase() === "localhost") { if (data.destination?.toLowerCase() === "localhost") {
aliasValue = `${cleanForFQDN(data.name)}.internal`; aliasValue = `${cleanForFQDN(data.name)}.internal`;
} }
data = { ...data, alias: aliasValue }; data = { ...data, alias: aliasValue };

View File

@@ -59,7 +59,7 @@ export default function EditInternalResourceDialog({
const currentAlias = data.alias?.trim() || ""; const currentAlias = data.alias?.trim() || "";
if (!currentAlias) { if (!currentAlias) {
let aliasValue = data.destination; let aliasValue = data.destination;
if (data.destination.toLowerCase() === "localhost") { if (data.destination?.toLowerCase() === "localhost") {
aliasValue = `${cleanForFQDN(data.name)}.internal`; aliasValue = `${cleanForFQDN(data.name)}.internal`;
} }
data = { ...data, alias: aliasValue }; data = { ...data, alias: aliasValue };

View File

@@ -124,8 +124,8 @@ export const getPortStringFromMode = (
return customValue; return customValue;
}; };
export const isHostname = (destination: string): boolean => export const isHostname = (destination: string | null): boolean =>
/[a-zA-Z]/.test(destination); !!destination && /[a-zA-Z]/.test(destination);
export const cleanForFQDN = (name: string): string => export const cleanForFQDN = (name: string): string =>
name name
@@ -147,7 +147,7 @@ export type InternalResourceData = {
mode: InternalResourceMode; mode: InternalResourceMode;
siteIds: number[]; siteIds: number[];
niceId: string; niceId: string;
destination: string; destination: string | null;
alias?: string | null; alias?: string | null;
tcpPortRangeString?: string | null; tcpPortRangeString?: string | null;
udpPortRangeString?: string | null; udpPortRangeString?: string | null;
@@ -179,7 +179,7 @@ export type InternalResourceFormValues = {
name: string; name: string;
siteIds: number[]; siteIds: number[];
mode: InternalResourceMode; mode: InternalResourceMode;
destination: string; destination: string | null;
alias?: string | null; alias?: string | null;
niceId?: string; niceId?: string;
tcpPortRangeString?: string | null; tcpPortRangeString?: string | null;
@@ -309,7 +309,7 @@ export function InternalResourceForm({
name: z.string().min(1, t(nameRequiredKey)).max(255, t(nameMaxKey)), name: z.string().min(1, t(nameRequiredKey)).max(255, t(nameMaxKey)),
siteIds: siteIdsSchema, siteIds: siteIdsSchema,
mode: z.enum(["host", "cidr", "http", "ssh"]), mode: z.enum(["host", "cidr", "http", "ssh"]),
destination: z.string(), destination: z.string().nullish(),
alias: z.string().nullish(), alias: z.string().nullish(),
destinationPort: z destinationPort: z
.number() .number()
@@ -352,9 +352,10 @@ export function InternalResourceForm({
.superRefine((data, ctx) => { .superRefine((data, ctx) => {
const isNativeSsh = const isNativeSsh =
data.mode === "ssh" && data.authDaemonMode === "native"; data.mode === "ssh" && data.authDaemonMode === "native";
const trimmedDestination = data.destination?.trim();
if ( if (
!isNativeSsh && !isNativeSsh &&
(!data.destination || data.destination.length < 1) (!trimmedDestination || trimmedDestination.length < 1)
) { ) {
ctx.addIssue({ ctx.addIssue({
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
@@ -735,9 +736,14 @@ export function InternalResourceForm({
<form <form
onSubmit={form.handleSubmit((values) => { onSubmit={form.handleSubmit((values) => {
const siteIds = values.siteIds; const siteIds = values.siteIds;
const trimmedDestination = values.destination?.trim();
onSubmit({ onSubmit({
...values, ...values,
siteIds, siteIds,
destination:
trimmedDestination && trimmedDestination.length > 0
? trimmedDestination
: null,
clients: (values.clients ?? []).map((c) => ({ clients: (values.clients ?? []).map((c) => ({
id: c.clientId.toString(), id: c.clientId.toString(),
text: c.name text: c.name
@@ -1097,10 +1103,25 @@ export function InternalResourceForm({
<Input <Input
{...field} {...field}
className="w-full" className="w-full"
value={
field.value ??
""
}
disabled={ disabled={
isHttpMode && isHttpMode &&
httpSectionDisabled httpSectionDisabled
} }
onChange={(e) =>
field.onChange(
e.target
.value ===
""
? null
: e
.target
.value
)
}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />

View File

@@ -68,7 +68,8 @@ function PrivateResourceMeta({ row }: { row: SiteResourceRow }) {
const modeLabel: Record<SiteResourceRow["mode"], string> = { const modeLabel: Record<SiteResourceRow["mode"], string> = {
host: t("editInternalResourceDialogModeHost"), host: t("editInternalResourceDialogModeHost"),
cidr: t("editInternalResourceDialogModeCidr"), cidr: t("editInternalResourceDialogModeCidr"),
http: t("editInternalResourceDialogModeHttp") http: t("editInternalResourceDialogModeHttp"),
ssh: t("editInternalResourceDialogModeSsh")
}; };
const dest = formatSiteResourceDestinationDisplay({ const dest = formatSiteResourceDestinationDisplay({
mode: row.mode, mode: row.mode,

View File

@@ -1,6 +1,6 @@
export type SiteResourceDestinationInput = { export type SiteResourceDestinationInput = {
mode: "host" | "cidr" | "http"; mode: "host" | "cidr" | "http" | "ssh";
destination: string; destination: string | null;
destinationPort: number | null; destinationPort: number | null;
scheme: "http" | "https" | null; scheme: "http" | "https" | null;
}; };
@@ -18,6 +18,9 @@ export function resolveHttpHttpsDisplayPort(
export function formatSiteResourceDestinationDisplay( export function formatSiteResourceDestinationDisplay(
row: SiteResourceDestinationInput row: SiteResourceDestinationInput
): string { ): string {
if (!row.destination) {
return "";
}
const { mode, destination, destinationPort, scheme } = row; const { mode, destination, destinationPort, scheme } = row;
if (mode !== "http") { if (mode !== "http") {
return destination; return destination;