Merge branch 'rdp-ssh' into dev

This commit is contained in:
Owen
2026-05-28 13:59:14 -07:00
104 changed files with 8491 additions and 3528 deletions

View File

@@ -44,14 +44,14 @@ const createSiteResourceSchema = z
name: z.string().min(1).max(255),
niceId: z.string().optional(),
// protocol: z.enum(["tcp", "udp"]).optional(),
mode: z.enum(["host", "cidr", "http"]),
mode: z.enum(["host", "cidr", "http", "ssh"]),
ssl: z.boolean().optional(), // only used for http mode
scheme: z.enum(["http", "https"]).optional(),
siteIds: z.array(z.int()).optional(),
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()
@@ -67,14 +67,18 @@ 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", "native"]).optional(),
pamMode: z.enum(["passthrough", "push"]).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") {
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()])
@@ -116,19 +120,45 @@ const createSiteResourceSchema = z
)
.refine(
(data) => {
if (data.mode !== "http") return true;
return (
data.scheme !== undefined &&
data.destinationPort !== undefined &&
data.destinationPort >= 1 &&
data.destinationPort <= 65535
);
if (data.mode === "http") {
return (
data.scheme !== undefined &&
data.scheme !== null &&
data.destinationPort !== undefined &&
data.destinationPort !== null &&
data.destinationPort >= 1 &&
data.destinationPort <= 65535
);
} else if (data.mode === "ssh") {
// just check the destinationPort
return (
data.destinationPort === undefined ||
(data.destinationPort !== null &&
data.destinationPort >= 1 &&
data.destinationPort <= 65535)
);
}
},
{
message:
"HTTP mode requires scheme (http or https) and a valid destination port"
}
)
.refine(
(data) => {
// destination is only optional for ssh mode with native authDaemonMode
if (data.mode === "ssh" && data.authDaemonMode === "native") {
return true;
}
return (
data.destination !== undefined && data.destination.trim() !== ""
);
},
{
message:
"Destination is required unless mode is ssh with authDaemonMode native"
}
)
.refine(
(data) => {
return (
@@ -212,6 +242,7 @@ export async function createSiteResource(
disableIcmp,
authDaemonPort,
authDaemonMode,
pamMode,
domainId,
subdomain
} = parsedBody.data;
@@ -276,8 +307,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(
@@ -366,132 +397,163 @@ export async function createSiteResource(
}
let aliasAddress: string | null = null;
let releaseAliasLock: (() => Promise<void>) | null = null;
if (mode === "host" || mode === "http") {
aliasAddress = await getNextAvailableAliasAddress(orgId);
const { value, release } =
await getNextAvailableAliasAddress(orgId);
aliasAddress = value;
releaseAliasLock = release;
}
let newSiteResource: SiteResource | undefined;
await db.transaction(async (trx) => {
const [network] = await trx
.insert(networks)
.values({
scope: "resource",
orgId: orgId
})
.returning();
try {
await db.transaction(async (trx) => {
const [network] = await trx
.insert(networks)
.values({
scope: "resource",
orgId: orgId
})
.returning();
if (!network) {
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
`Failed to create network`
)
);
}
// Create the site resource
const insertValues: typeof siteResources.$inferInsert = {
niceId: updatedNiceId!,
orgId,
name,
mode,
ssl,
networkId: network.networkId,
destination,
scheme,
destinationPort,
enabled,
alias: alias ? alias.trim() : null,
aliasAddress,
tcpPortRangeString:
mode == "http" ? "443,80" : tcpPortRangeString,
udpPortRangeString: mode == "http" ? "" : udpPortRangeString,
disableIcmp: disableIcmp || (mode == "http" ? true : false), // default to true for http resources, otherwise false
domainId,
subdomain: finalSubdomain,
fullDomain
};
if (isLicensedSshPam) {
if (authDaemonPort !== undefined)
insertValues.authDaemonPort = authDaemonPort;
if (authDaemonMode !== undefined)
insertValues.authDaemonMode = authDaemonMode;
}
[newSiteResource] = await trx
.insert(siteResources)
.values(insertValues)
.returning();
const siteResourceId = newSiteResource.siteResourceId;
//////////////////// update the associations ////////////////////
for (const siteId of siteIds) {
await trx.insert(siteNetworks).values({
siteId: siteId,
networkId: network.networkId
});
}
const [adminRole] = await trx
.select()
.from(roles)
.where(and(eq(roles.isAdmin, true), eq(roles.orgId, orgId)))
.limit(1);
if (!adminRole) {
return next(
createHttpError(HttpCode.NOT_FOUND, `Admin role not found`)
);
}
await trx.insert(roleSiteResources).values({
roleId: adminRole.roleId,
siteResourceId: siteResourceId
});
if (roleIds.length > 0) {
await trx
.insert(roleSiteResources)
.values(
roleIds.map((roleId) => ({ roleId, siteResourceId }))
);
}
if (userIds.length > 0) {
await trx
.insert(userSiteResources)
.values(
userIds.map((userId) => ({ userId, siteResourceId }))
);
}
if (clientIds.length > 0) {
await trx.insert(clientSiteResources).values(
clientIds.map((clientId) => ({
clientId,
siteResourceId
}))
);
}
for (const siteToAssign of sitesToAssign) {
const [newt] = await trx
.select()
.from(newts)
.where(eq(newts.siteId, siteToAssign.siteId))
.limit(1);
if (!newt) {
if (!network) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Newt not found for site ${siteToAssign.siteId}`
HttpCode.INTERNAL_SERVER_ERROR,
`Failed to create network`
)
);
}
}
});
let tcpPortRangeStringAdjusted = tcpPortRangeString;
if (mode === "http") {
tcpPortRangeStringAdjusted = "443,80";
} else if (mode === "ssh") {
tcpPortRangeStringAdjusted = destinationPort
? destinationPort.toString()
: "22";
}
// Create the site resource
const insertValues: typeof siteResources.$inferInsert = {
niceId: updatedNiceId!,
orgId,
name,
mode,
ssl,
networkId: network.networkId,
destination: destination, // the ssh can be null
scheme,
destinationPort,
enabled,
alias: alias ? alias.trim() : null,
aliasAddress,
tcpPortRangeString: tcpPortRangeStringAdjusted,
udpPortRangeString:
mode == "http" || mode == "ssh"
? ""
: udpPortRangeString,
disableIcmp:
disableIcmp ||
(mode == "http" || mode == "ssh" ? true : false), // default to true for http resources, otherwise false
domainId,
subdomain: finalSubdomain,
fullDomain
};
if (isLicensedSshPam) {
if (authDaemonPort !== undefined)
insertValues.authDaemonPort = authDaemonPort;
if (authDaemonMode !== undefined)
insertValues.authDaemonMode = authDaemonMode;
if (pamMode !== undefined) insertValues.pamMode = pamMode;
}
[newSiteResource] = await trx
.insert(siteResources)
.values(insertValues)
.returning();
const siteResourceId = newSiteResource.siteResourceId;
//////////////////// update the associations ////////////////////
for (const siteId of siteIds) {
await trx.insert(siteNetworks).values({
siteId: siteId,
networkId: network.networkId
});
}
const [adminRole] = await trx
.select()
.from(roles)
.where(and(eq(roles.isAdmin, true), eq(roles.orgId, orgId)))
.limit(1);
if (!adminRole) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Admin role not found`
)
);
}
await trx.insert(roleSiteResources).values({
roleId: adminRole.roleId,
siteResourceId: siteResourceId
});
if (roleIds.length > 0) {
await trx
.insert(roleSiteResources)
.values(
roleIds.map((roleId) => ({
roleId,
siteResourceId
}))
);
}
if (userIds.length > 0) {
await trx
.insert(userSiteResources)
.values(
userIds.map((userId) => ({
userId,
siteResourceId
}))
);
}
if (clientIds.length > 0) {
await trx.insert(clientSiteResources).values(
clientIds.map((clientId) => ({
clientId,
siteResourceId
}))
);
}
for (const siteToAssign of sitesToAssign) {
const [newt] = await trx
.select()
.from(newts)
.where(eq(newts.siteId, siteToAssign.siteId))
.limit(1);
if (!newt) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Newt not found for site ${siteToAssign.siteId}`
)
);
}
}
});
} finally {
await releaseAliasLock?.();
}
if (!newSiteResource) {
return next(

View File

@@ -56,7 +56,7 @@ const updateSiteResourceSchema = z
)
.optional(),
// mode: z.enum(["host", "cidr", "port"]).optional(),
mode: z.enum(["host", "cidr", "http"]).optional(),
mode: z.enum(["host", "cidr", "http", "ssh"]).optional(),
ssl: z.boolean().optional(),
scheme: z.enum(["http", "https"]).nullish(),
// proxyPort: z.int().positive().nullish(),
@@ -77,14 +77,18 @@ 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", "native"]).optional(),
pamMode: z.enum(["passthrough", "push"]).optional(),
domainId: z.string().optional(),
subdomain: z.string().optional()
})
.strict()
.refine(
(data) => {
if (data.mode === "host" && data.destination) {
if (
(data.mode === "host" || data.mode == "ssh") &&
data.destination
) {
const isValidIP = z
// .union([z.ipv4(), z.ipv6()])
.union([z.ipv4()]) // for now lets just do ipv4 until we verify ipv6 works everywhere
@@ -125,21 +129,45 @@ const updateSiteResourceSchema = z
)
.refine(
(data) => {
if (data.mode !== "http") return true;
return (
data.scheme !== undefined &&
data.scheme !== null &&
data.destinationPort !== undefined &&
data.destinationPort !== null &&
data.destinationPort >= 1 &&
data.destinationPort <= 65535
);
if (data.mode === "http") {
return (
data.scheme !== undefined &&
data.scheme !== null &&
data.destinationPort !== undefined &&
data.destinationPort !== null &&
data.destinationPort >= 1 &&
data.destinationPort <= 65535
);
} else if (data.mode === "ssh") {
// just check the destinationPort
return (
data.destinationPort === undefined ||
(data.destinationPort !== null &&
data.destinationPort >= 1 &&
data.destinationPort <= 65535)
);
}
},
{
message:
"HTTP mode requires scheme (http or https) and a valid destination port"
}
)
.refine(
(data) => {
// destination is only optional for ssh mode with native authDaemonMode
if (data.mode === "ssh" && data.authDaemonMode === "native") {
return true;
}
return (
data.destination !== undefined && data.destination.trim() !== ""
);
},
{
message:
"Destination is required unless mode is ssh with authDaemonMode native"
}
)
.refine(
(data) => {
return (
@@ -222,6 +250,7 @@ export async function updateSiteResource(
disableIcmp,
authDaemonPort,
authDaemonMode,
pamMode,
domainId,
subdomain
} = parsedBody.data;
@@ -430,16 +459,30 @@ export async function updateSiteResource(
const sshPamSet =
isLicensedSshPam &&
(authDaemonPort !== undefined ||
authDaemonMode !== undefined)
authDaemonMode !== undefined ||
pamMode !== undefined)
? {
...(authDaemonPort !== undefined && {
authDaemonPort
}),
...(authDaemonMode !== undefined && {
authDaemonMode
}),
...(pamMode !== undefined && {
pamMode
})
}
: {};
let tcpPortRangeStringAdjusted = tcpPortRangeString;
if (mode === "http") {
tcpPortRangeStringAdjusted = "443,80";
} else if (mode === "ssh") {
tcpPortRangeStringAdjusted = destinationPort
? destinationPort.toString()
: "22";
}
[updatedSiteResource] = await trx
.update(siteResources)
.set({
@@ -452,12 +495,14 @@ export async function updateSiteResource(
destinationPort,
enabled,
alias: alias ? alias.trim() : null,
tcpPortRangeString:
mode == "http" ? "443,80" : tcpPortRangeString,
tcpPortRangeString: tcpPortRangeStringAdjusted,
udpPortRangeString:
mode == "http" ? "" : udpPortRangeString,
mode == "http" || mode == "ssh"
? ""
: udpPortRangeString,
disableIcmp:
disableIcmp || (mode == "http" ? true : false), // default to true for http resources, otherwise false
disableIcmp ||
(mode == "http" || mode == "ssh" ? true : false), // default to true for http resources, otherwise false
domainId,
subdomain: finalSubdomain,
fullDomain,
@@ -554,13 +599,17 @@ export async function updateSiteResource(
const sshPamSet =
isLicensedSshPam &&
(authDaemonPort !== undefined ||
authDaemonMode !== undefined)
authDaemonMode !== undefined ||
pamMode !== undefined)
? {
...(authDaemonPort !== undefined && {
authDaemonPort
}),
...(authDaemonMode !== undefined && {
authDaemonMode
}),
...(pamMode !== undefined && {
pamMode
})
}
: {};
@@ -832,6 +881,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)