mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-28 03:32:20 +00:00
Fix cascading errors
This commit is contained in:
@@ -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"),
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
@@ -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 />
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user