mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-22 21:06:37 +00:00
Build client site resource associations and send messages
This commit is contained in:
@@ -654,7 +654,7 @@ export const clients = pgTable("clients", {
|
||||
maxConnections: integer("maxConnections")
|
||||
});
|
||||
|
||||
export const clientSites = pgTable("clientSites", {
|
||||
export const clientSitesAssociationsCache = pgTable("clientSitesAssociationsCache", {
|
||||
clientId: integer("clientId")
|
||||
.notNull()
|
||||
.references(() => clients.clientId, { onDelete: "cascade" }),
|
||||
@@ -665,6 +665,15 @@ export const clientSites = pgTable("clientSites", {
|
||||
endpoint: varchar("endpoint")
|
||||
});
|
||||
|
||||
export const clientSiteResourcesAssociationsCache = pgTable("clientSiteResourcesAssociationsCache", {
|
||||
clientId: integer("clientId")
|
||||
.notNull()
|
||||
.references(() => clients.clientId, { onDelete: "cascade" }),
|
||||
siteResourceId: integer("siteResourceId")
|
||||
.notNull()
|
||||
.references(() => siteResources.siteResourceId, { onDelete: "cascade" })
|
||||
});
|
||||
|
||||
export const olms = pgTable("olms", {
|
||||
olmId: varchar("id").primaryKey(),
|
||||
secretHash: varchar("secretHash").notNull(),
|
||||
@@ -847,7 +856,7 @@ export type ApiKey = InferSelectModel<typeof apiKeys>;
|
||||
export type ApiKeyAction = InferSelectModel<typeof apiKeyActions>;
|
||||
export type ApiKeyOrg = InferSelectModel<typeof apiKeyOrg>;
|
||||
export type Client = InferSelectModel<typeof clients>;
|
||||
export type ClientSite = InferSelectModel<typeof clientSites>;
|
||||
export type ClientSite = InferSelectModel<typeof clientSitesAssociationsCache>;
|
||||
export type Olm = InferSelectModel<typeof olms>;
|
||||
export type OlmSession = InferSelectModel<typeof olmSessions>;
|
||||
export type UserClient = InferSelectModel<typeof userClients>;
|
||||
|
||||
@@ -361,7 +361,7 @@ export const clients = sqliteTable("clients", {
|
||||
lastHolePunch: integer("lastHolePunch")
|
||||
});
|
||||
|
||||
export const clientSites = sqliteTable("clientSites", {
|
||||
export const clientSitesAssociationsCache = sqliteTable("clientSitesAssociationsCache", {
|
||||
clientId: integer("clientId")
|
||||
.notNull()
|
||||
.references(() => clients.clientId, { onDelete: "cascade" }),
|
||||
@@ -374,6 +374,15 @@ export const clientSites = sqliteTable("clientSites", {
|
||||
endpoint: text("endpoint")
|
||||
});
|
||||
|
||||
export const clientSiteResourcesAssociationsCache = sqliteTable("clientSiteResourcesAssociationsCache", {
|
||||
clientId: integer("clientId")
|
||||
.notNull()
|
||||
.references(() => clients.clientId, { onDelete: "cascade" }),
|
||||
siteResourceId: integer("siteResourceId")
|
||||
.notNull()
|
||||
.references(() => siteResources.siteResourceId, { onDelete: "cascade" })
|
||||
});
|
||||
|
||||
export const olms = sqliteTable("olms", {
|
||||
olmId: text("id").primaryKey(),
|
||||
secretHash: text("secretHash").notNull(),
|
||||
@@ -894,7 +903,7 @@ export type ResourceRule = InferSelectModel<typeof resourceRules>;
|
||||
export type Domain = InferSelectModel<typeof domains>;
|
||||
export type DnsRecord = InferSelectModel<typeof dnsRecords>;
|
||||
export type Client = InferSelectModel<typeof clients>;
|
||||
export type ClientSite = InferSelectModel<typeof clientSites>;
|
||||
export type ClientSite = InferSelectModel<typeof clientSitesAssociationsCache>;
|
||||
export type RoleClient = InferSelectModel<typeof roleClients>;
|
||||
export type UserClient = InferSelectModel<typeof userClients>;
|
||||
export type SupporterKey = InferSelectModel<typeof supporterKey>;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { clients, clientSites, db, olms, orgs, roleClients, roles, userClients, userOrgs, Transaction } from "@server/db";
|
||||
import { clients, clientSitesAssociationsCache, db, olms, orgs, roleClients, roles, userClients, userOrgs, Transaction } from "@server/db";
|
||||
import { eq, and, notInArray } from "drizzle-orm";
|
||||
import { listExitNodes } from "#dynamic/lib/exitNodes";
|
||||
import { getNextAvailableClientSubnet } from "@server/lib/ip";
|
||||
@@ -228,8 +228,8 @@ async function cleanupOrphanedClients(
|
||||
// Delete client-site associations first, then delete the clients
|
||||
for (const client of clientsToDelete) {
|
||||
await trx
|
||||
.delete(clientSites)
|
||||
.where(eq(clientSites.clientId, client.clientId));
|
||||
.delete(clientSitesAssociationsCache)
|
||||
.where(eq(clientSitesAssociationsCache.clientId, client.clientId));
|
||||
}
|
||||
|
||||
if (clientsToDelete.length > 0) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { clientSites, db, SiteResource, Transaction } from "@server/db";
|
||||
import { clientSitesAssociationsCache, db, SiteResource, Transaction } from "@server/db";
|
||||
import { clients, orgs, sites } from "@server/db";
|
||||
import { and, eq, isNotNull } from "drizzle-orm";
|
||||
import config from "@server/lib/config";
|
||||
import z from "zod";
|
||||
import { getClientSiteResourceAccess } from "./rebuildSiteClientAssociations";
|
||||
import { getClientSiteResourceAccess } from "./rebuildClientAssociations";
|
||||
import logger from "@server/logger";
|
||||
|
||||
interface IPRange {
|
||||
@@ -338,41 +338,54 @@ export type SubnetProxyTarget = {
|
||||
}[];
|
||||
};
|
||||
|
||||
export async function generateSubnetProxyTargets(
|
||||
allSiteResources: SiteResource[],
|
||||
trx: Transaction | typeof db = db
|
||||
): Promise<SubnetProxyTarget[]> {
|
||||
export function generateSingleSubnetProxyTargets(
|
||||
siteResource: SiteResource,
|
||||
clients: {
|
||||
clientId: number;
|
||||
pubKey: string | null;
|
||||
subnet: string | null;
|
||||
}[]
|
||||
): SubnetProxyTarget[] {
|
||||
let targets: SubnetProxyTarget[] = [];
|
||||
|
||||
for (const siteResource of allSiteResources) {
|
||||
const { mergedAllClients } =
|
||||
await getClientSiteResourceAccess(siteResource, trx);
|
||||
if (clients.length === 0) {
|
||||
logger.debug(
|
||||
`No clients have access to site resource ${siteResource.siteResourceId}, skipping target generation.`
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
if (mergedAllClients.length === 0) {
|
||||
logger.debug(`No clients have access to site resource ${siteResource.siteResourceId}, skipping target generation.`);
|
||||
for (const clientSite of clients) {
|
||||
if (!clientSite.subnet) {
|
||||
logger.debug(
|
||||
`Client ${clientSite.clientId} has no subnet, skipping for site resource ${siteResource.siteResourceId}.`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const clientSite of mergedAllClients) {
|
||||
const clientPrefix = `${clientSite.subnet.split("/")[0]}/32`;
|
||||
const clientPrefix = `${clientSite.subnet.split("/")[0]}/32`;
|
||||
|
||||
if (siteResource.mode == "host") {
|
||||
// check if this is a valid ip
|
||||
const ipSchema = z.union([z.ipv4(), z.ipv6()]);
|
||||
if (ipSchema.safeParse(siteResource.destination).success) {
|
||||
targets.push({
|
||||
sourcePrefix: clientPrefix,
|
||||
destPrefix: `${siteResource.destination}/32`
|
||||
});
|
||||
}
|
||||
} else if (siteResource.mode == "cidr") {
|
||||
if (siteResource.mode == "host") {
|
||||
// check if this is a valid ip
|
||||
const ipSchema = z.union([z.ipv4(), z.ipv6()]);
|
||||
if (ipSchema.safeParse(siteResource.destination).success) {
|
||||
targets.push({
|
||||
sourcePrefix: clientPrefix,
|
||||
destPrefix: siteResource.destination
|
||||
destPrefix: `${siteResource.destination}/32`
|
||||
});
|
||||
}
|
||||
} else if (siteResource.mode == "cidr") {
|
||||
targets.push({
|
||||
sourcePrefix: clientPrefix,
|
||||
destPrefix: siteResource.destination
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// print a nice representation of the targets
|
||||
logger.debug(
|
||||
`Generated subnet proxy targets for: ${JSON.stringify(targets, null, 2)}`
|
||||
);
|
||||
|
||||
return targets;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
Client,
|
||||
clients,
|
||||
clientSiteResources,
|
||||
clientSites,
|
||||
clientSitesAssociationsCache,
|
||||
db,
|
||||
exitNodes,
|
||||
newts,
|
||||
@@ -29,7 +29,15 @@ import {
|
||||
} from "@server/routers/olm/peers";
|
||||
import { sendToExitNode } from "#dynamic/lib/exitNodes";
|
||||
import logger from "@server/logger";
|
||||
import { generateRemoteSubnetsStr } from "@server/lib/ip";
|
||||
import {
|
||||
generateRemoteSubnetsStr,
|
||||
generateSingleSubnetProxyTargets,
|
||||
SubnetProxyTarget
|
||||
} from "@server/lib/ip";
|
||||
import {
|
||||
addTargets as addSubnetProxyTargets,
|
||||
removeTargets as removeSubnetProxyTargets
|
||||
} from "@server/routers/client/targets";
|
||||
|
||||
export async function getClientSiteResourceAccess(
|
||||
siteResource: SiteResource,
|
||||
@@ -117,21 +125,29 @@ export async function getClientSiteResourceAccess(
|
||||
};
|
||||
}
|
||||
|
||||
export async function rebuildSiteClientAssociations(
|
||||
export async function rebuildClientAssociations(
|
||||
siteResource: SiteResource,
|
||||
trx: Transaction | typeof db = db
|
||||
): Promise<void> {
|
||||
): Promise<{
|
||||
mergedAllClients: {
|
||||
clientId: number;
|
||||
pubKey: string | null;
|
||||
subnet: string | null;
|
||||
}[];
|
||||
}> {
|
||||
const siteId = siteResource.siteId;
|
||||
|
||||
const { site, mergedAllClients, mergedAllClientIds } =
|
||||
await getClientSiteResourceAccess(siteResource, trx);
|
||||
|
||||
/////////// process the client-site associations ///////////
|
||||
|
||||
const existingClientSites = await trx
|
||||
.select({
|
||||
clientId: clientSites.clientId
|
||||
clientId: clientSitesAssociationsCache.clientId
|
||||
})
|
||||
.from(clientSites)
|
||||
.where(eq(clientSites.siteId, siteResource.siteId));
|
||||
.from(clientSitesAssociationsCache)
|
||||
.where(eq(clientSitesAssociationsCache.siteId, siteResource.siteId));
|
||||
|
||||
const existingClientSiteIds = existingClientSites.map(
|
||||
(row) => row.clientId
|
||||
@@ -153,15 +169,16 @@ export async function rebuildSiteClientAssociations(
|
||||
(clientId) => !existingClientSiteIds.includes(clientId)
|
||||
);
|
||||
|
||||
const clientSitesToInsert = mergedAllClientIds
|
||||
.filter((clientId) => !existingClientSiteIds.includes(clientId))
|
||||
.map((clientId) => ({
|
||||
clientId,
|
||||
siteId
|
||||
}));
|
||||
const clientSitesToInsert = clientSitesToAdd.map((clientId) => ({
|
||||
clientId,
|
||||
siteId
|
||||
}));
|
||||
|
||||
if (clientSitesToInsert.length > 0) {
|
||||
await trx.insert(clientSites).values(clientSitesToInsert);
|
||||
await trx
|
||||
.insert(clientSitesAssociationsCache)
|
||||
.values(clientSitesToInsert)
|
||||
.returning();
|
||||
}
|
||||
|
||||
// Now remove any client-site associations that should no longer exist
|
||||
@@ -171,11 +188,68 @@ export async function rebuildSiteClientAssociations(
|
||||
|
||||
if (clientSitesToRemove.length > 0) {
|
||||
await trx
|
||||
.delete(clientSites)
|
||||
.delete(clientSitesAssociationsCache)
|
||||
.where(
|
||||
and(
|
||||
eq(clientSites.siteId, siteId),
|
||||
inArray(clientSites.clientId, clientSitesToRemove)
|
||||
eq(clientSitesAssociationsCache.siteId, siteId),
|
||||
inArray(
|
||||
clientSitesAssociationsCache.clientId,
|
||||
clientSitesToRemove
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/////////// process the client-siteResource associations ///////////
|
||||
|
||||
const existingClientSiteResources = await trx
|
||||
.select({
|
||||
clientId: clientSiteResources.clientId
|
||||
})
|
||||
.from(clientSiteResources)
|
||||
.where(
|
||||
eq(clientSiteResources.siteResourceId, siteResource.siteResourceId)
|
||||
);
|
||||
|
||||
const existingClientSiteResourceIds = existingClientSiteResources.map(
|
||||
(row) => row.clientId
|
||||
);
|
||||
|
||||
const clientSiteResourcesToAdd = mergedAllClientIds.filter(
|
||||
(clientId) => !existingClientSiteResourceIds.includes(clientId)
|
||||
);
|
||||
|
||||
const clientSiteResourcesToInsert = clientSiteResourcesToAdd.map(
|
||||
(clientId) => ({
|
||||
clientId,
|
||||
siteResourceId: siteResource.siteResourceId
|
||||
})
|
||||
);
|
||||
|
||||
if (clientSiteResourcesToInsert.length > 0) {
|
||||
await trx
|
||||
.insert(clientSiteResources)
|
||||
.values(clientSiteResourcesToInsert)
|
||||
.returning();
|
||||
}
|
||||
|
||||
const clientSiteResourcesToRemove = existingClientSiteResourceIds.filter(
|
||||
(clientId) => !mergedAllClientIds.includes(clientId)
|
||||
);
|
||||
|
||||
if (clientSiteResourcesToRemove.length > 0) {
|
||||
await trx
|
||||
.delete(clientSiteResources)
|
||||
.where(
|
||||
and(
|
||||
eq(
|
||||
clientSiteResources.siteResourceId,
|
||||
siteResource.siteResourceId
|
||||
),
|
||||
inArray(
|
||||
clientSiteResources.clientId,
|
||||
clientSiteResourcesToRemove
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -190,6 +264,20 @@ export async function rebuildSiteClientAssociations(
|
||||
clientSitesToRemove,
|
||||
trx
|
||||
);
|
||||
|
||||
// Handle subnet proxy target updates for the resource associations
|
||||
await handleSubnetProxyTargetUpdates(
|
||||
siteResource,
|
||||
mergedAllClients,
|
||||
existingClients,
|
||||
clientSiteResourcesToAdd,
|
||||
clientSiteResourcesToRemove,
|
||||
trx
|
||||
);
|
||||
|
||||
return {
|
||||
mergedAllClients
|
||||
}
|
||||
}
|
||||
|
||||
async function handleMessagesForSiteClients(
|
||||
@@ -401,9 +489,12 @@ export async function updateClientSiteDestinations(
|
||||
const sitesData = await trx
|
||||
.select()
|
||||
.from(sites)
|
||||
.innerJoin(clientSites, eq(sites.siteId, clientSites.siteId))
|
||||
.innerJoin(
|
||||
clientSitesAssociationsCache,
|
||||
eq(sites.siteId, clientSitesAssociationsCache.siteId)
|
||||
)
|
||||
.leftJoin(exitNodes, eq(sites.exitNodeId, exitNodes.exitNodeId))
|
||||
.where(eq(clientSites.clientId, client.clientId));
|
||||
.where(eq(clientSitesAssociationsCache.clientId, client.clientId));
|
||||
|
||||
for (const site of sitesData) {
|
||||
if (!site.sites.subnet) {
|
||||
@@ -411,7 +502,7 @@ export async function updateClientSiteDestinations(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!site.clientSites.endpoint) {
|
||||
if (!site.clientSitesAssociationsCache.endpoint) {
|
||||
logger.warn(`Site ${site.sites.siteId} has no endpoint, skipping`); // if this is a new association the endpoint is not set yet // TODO: FIX THIS
|
||||
continue;
|
||||
}
|
||||
@@ -427,9 +518,9 @@ export async function updateClientSiteDestinations(
|
||||
exitNodeId: site.exitNodes?.exitNodeId || 0,
|
||||
type: site.exitNodes?.type || "",
|
||||
name: site.exitNodes?.name || "",
|
||||
sourceIp: site.clientSites.endpoint.split(":")[0] || "",
|
||||
sourceIp: site.clientSitesAssociationsCache.endpoint.split(":")[0] || "",
|
||||
sourcePort:
|
||||
parseInt(site.clientSites.endpoint.split(":")[1]) || 0,
|
||||
parseInt(site.clientSitesAssociationsCache.endpoint.split(":")[1]) || 0,
|
||||
destinations: [
|
||||
{
|
||||
destinationIP: site.sites.subnet.split("/")[0],
|
||||
@@ -481,3 +572,76 @@ export async function updateClientSiteDestinations(
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubnetProxyTargetUpdates(
|
||||
siteResource: SiteResource,
|
||||
allClients: {
|
||||
clientId: number;
|
||||
pubKey: string | null;
|
||||
subnet: string | null;
|
||||
}[],
|
||||
existingClients: {
|
||||
clientId: number;
|
||||
pubKey: string | null;
|
||||
subnet: string | null;
|
||||
}[],
|
||||
clientSiteResourcesToAdd: number[],
|
||||
clientSiteResourcesToRemove: number[],
|
||||
trx: Transaction | typeof db = db
|
||||
): Promise<void> {
|
||||
// Get the newt for this site
|
||||
const [newt] = await trx
|
||||
.select()
|
||||
.from(newts)
|
||||
.where(eq(newts.siteId, siteResource.siteId))
|
||||
.limit(1);
|
||||
|
||||
if (!newt) {
|
||||
logger.warn(
|
||||
`Newt not found for site ${siteResource.siteId}, skipping subnet proxy target updates`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate targets for added associations
|
||||
if (clientSiteResourcesToAdd.length > 0) {
|
||||
const addedClients = allClients.filter((client) =>
|
||||
clientSiteResourcesToAdd.includes(client.clientId)
|
||||
);
|
||||
|
||||
if (addedClients.length > 0) {
|
||||
const targetsToAdd = generateSingleSubnetProxyTargets(
|
||||
siteResource,
|
||||
addedClients
|
||||
);
|
||||
|
||||
if (targetsToAdd.length > 0) {
|
||||
logger.info(
|
||||
`Adding ${targetsToAdd.length} subnet proxy targets for siteResource ${siteResource.siteResourceId}`
|
||||
);
|
||||
await addSubnetProxyTargets(newt.newtId, targetsToAdd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate targets for removed associations
|
||||
if (clientSiteResourcesToRemove.length > 0) {
|
||||
const removedClients = existingClients.filter((client) =>
|
||||
clientSiteResourcesToRemove.includes(client.clientId)
|
||||
);
|
||||
|
||||
if (removedClients.length > 0) {
|
||||
const targetsToRemove = generateSingleSubnetProxyTargets(
|
||||
siteResource,
|
||||
removedClients
|
||||
);
|
||||
|
||||
if (targetsToRemove.length > 0) {
|
||||
logger.info(
|
||||
`Removing ${targetsToRemove.length} subnet proxy targets for siteResource ${siteResource.siteResourceId}`
|
||||
);
|
||||
await removeSubnetProxyTargets(newt.newtId, targetsToRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -345,9 +345,9 @@ export async function getTraefikConfig(
|
||||
routerMiddlewares.push(rewriteMiddlewareName);
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`Created path rewrite middleware ${rewriteMiddlewareName}: ${resource.pathMatchType}(${resource.path}) -> ${resource.rewritePathType}(${resource.rewritePath})`
|
||||
);
|
||||
// logger.debug(
|
||||
// `Created path rewrite middleware ${rewriteMiddlewareName}: ${resource.pathMatchType}(${resource.path}) -> ${resource.rewritePathType}(${resource.rewritePath})`
|
||||
// );
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Failed to create path rewrite middleware for resource ${resource.resourceId}: ${error}`
|
||||
|
||||
@@ -434,9 +434,9 @@ export async function getTraefikConfig(
|
||||
routerMiddlewares.push(rewriteMiddlewareName);
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`Created path rewrite middleware ${rewriteMiddlewareName}: ${resource.pathMatchType}(${resource.path}) -> ${resource.rewritePathType}(${resource.rewritePath})`
|
||||
);
|
||||
// logger.debug(
|
||||
// `Created path rewrite middleware ${rewriteMiddlewareName}: ${resource.pathMatchType}(${resource.path}) -> ${resource.rewritePathType}(${resource.rewritePath})`
|
||||
// );
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Failed to create path rewrite middleware for resource ${resource.resourceId}: ${error}`
|
||||
|
||||
@@ -69,9 +69,9 @@ export async function cleanUpOldLogs(orgId: string, retentionDays: number) {
|
||||
)
|
||||
);
|
||||
|
||||
logger.debug(
|
||||
`Cleaned up request audit logs older than ${retentionDays} days`
|
||||
);
|
||||
// logger.debug(
|
||||
// `Cleaned up request audit logs older than ${retentionDays} days`
|
||||
// );
|
||||
} catch (error) {
|
||||
logger.error("Error cleaning up old request audit logs:", error);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { db } from "@server/db";
|
||||
import { clients, clientSites } from "@server/db";
|
||||
import { clients, clientSitesAssociationsCache } from "@server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
@@ -70,8 +70,8 @@ export async function deleteClient(
|
||||
await db.transaction(async (trx) => {
|
||||
// Delete the client-site associations first
|
||||
await trx
|
||||
.delete(clientSites)
|
||||
.where(eq(clientSites.clientId, clientId));
|
||||
.delete(clientSitesAssociationsCache)
|
||||
.where(eq(clientSitesAssociationsCache.clientId, clientId));
|
||||
|
||||
// Then delete the client itself
|
||||
await trx.delete(clients).where(eq(clients.clientId, clientId));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { db } from "@server/db";
|
||||
import { clients, clientSites } from "@server/db";
|
||||
import { clients, clientSitesAssociationsCache } from "@server/db";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
@@ -29,9 +29,9 @@ async function query(clientId: number) {
|
||||
|
||||
// Get the siteIds associated with this client
|
||||
const sites = await db
|
||||
.select({ siteId: clientSites.siteId })
|
||||
.from(clientSites)
|
||||
.where(eq(clientSites.clientId, clientId));
|
||||
.select({ siteId: clientSitesAssociationsCache.siteId })
|
||||
.from(clientSitesAssociationsCache)
|
||||
.where(eq(clientSitesAssociationsCache.clientId, clientId));
|
||||
|
||||
// Add the siteIds to the client object
|
||||
return {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
roleClients,
|
||||
sites,
|
||||
userClients,
|
||||
clientSites
|
||||
clientSitesAssociationsCache
|
||||
} from "@server/db";
|
||||
import logger from "@server/logger";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
@@ -142,14 +142,14 @@ async function getSiteAssociations(clientIds: number[]) {
|
||||
|
||||
return db
|
||||
.select({
|
||||
clientId: clientSites.clientId,
|
||||
siteId: clientSites.siteId,
|
||||
clientId: clientSitesAssociationsCache.clientId,
|
||||
siteId: clientSitesAssociationsCache.siteId,
|
||||
siteName: sites.name,
|
||||
siteNiceId: sites.niceId
|
||||
})
|
||||
.from(clientSites)
|
||||
.leftJoin(sites, eq(clientSites.siteId, sites.siteId))
|
||||
.where(inArray(clientSites.clientId, clientIds));
|
||||
.from(clientSitesAssociationsCache)
|
||||
.leftJoin(sites, eq(clientSitesAssociationsCache.siteId, sites.siteId))
|
||||
.where(inArray(clientSitesAssociationsCache.clientId, clientIds));
|
||||
}
|
||||
|
||||
type OlmWithUpdateAvailable = Awaited<ReturnType<typeof queryClients>>[0] & {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { SubnetProxyTarget } from "@server/lib/ip";
|
||||
|
||||
export async function addTargets(newtId: string, targets: SubnetProxyTarget[]) {
|
||||
await sendToClient(newtId, {
|
||||
type: `newt/wg/target/add`,
|
||||
type: `newt/wg/targets/add`,
|
||||
data: targets
|
||||
});
|
||||
}
|
||||
@@ -13,7 +13,7 @@ export async function removeTargets(
|
||||
targets: SubnetProxyTarget[]
|
||||
) {
|
||||
await sendToClient(newtId, {
|
||||
type: `newt/wg/target/remove`,
|
||||
type: `newt/wg/targets/remove`,
|
||||
data: targets
|
||||
});
|
||||
}
|
||||
@@ -26,7 +26,7 @@ export async function updateTargets(
|
||||
}
|
||||
) {
|
||||
await sendToClient(newtId, {
|
||||
type: `newt/wg/target/update`,
|
||||
type: `newt/wg/targets/update`,
|
||||
data: targets
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { Client, db, exitNodes, olms, sites } from "@server/db";
|
||||
import { clients, clientSites } from "@server/db";
|
||||
import { clients, clientSitesAssociationsCache } from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import createHttpError from "http-errors";
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
olms,
|
||||
Site,
|
||||
sites,
|
||||
clientSites,
|
||||
clientSitesAssociationsCache,
|
||||
ExitNode
|
||||
} from "@server/db";
|
||||
import { db } from "@server/db";
|
||||
@@ -109,8 +109,8 @@ export async function generateRelayMappings(exitNode: ExitNode) {
|
||||
// Find all clients associated with this site through clientSites
|
||||
const clientSitesRes = await db
|
||||
.select()
|
||||
.from(clientSites)
|
||||
.where(eq(clientSites.siteId, site.siteId));
|
||||
.from(clientSitesAssociationsCache)
|
||||
.where(eq(clientSitesAssociationsCache.siteId, site.siteId));
|
||||
|
||||
for (const clientSite of clientSitesRes) {
|
||||
if (!clientSite.endpoint) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
olms,
|
||||
Site,
|
||||
sites,
|
||||
clientSites,
|
||||
clientSitesAssociationsCache,
|
||||
exitNodes,
|
||||
ExitNode
|
||||
} from "@server/db";
|
||||
@@ -174,11 +174,11 @@ export async function updateAndGenerateEndpointDestinations(
|
||||
listenPort: sites.listenPort
|
||||
})
|
||||
.from(sites)
|
||||
.innerJoin(clientSites, eq(sites.siteId, clientSites.siteId))
|
||||
.innerJoin(clientSitesAssociationsCache, eq(sites.siteId, clientSitesAssociationsCache.siteId))
|
||||
.where(
|
||||
and(
|
||||
eq(sites.exitNodeId, exitNode.exitNodeId),
|
||||
eq(clientSites.clientId, olm.clientId)
|
||||
eq(clientSitesAssociationsCache.clientId, olm.clientId)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -189,14 +189,14 @@ export async function updateAndGenerateEndpointDestinations(
|
||||
);
|
||||
|
||||
await db
|
||||
.update(clientSites)
|
||||
.update(clientSitesAssociationsCache)
|
||||
.set({
|
||||
endpoint: `${ip}:${port}`
|
||||
})
|
||||
.where(
|
||||
and(
|
||||
eq(clientSites.clientId, olm.clientId),
|
||||
eq(clientSites.siteId, site.siteId)
|
||||
eq(clientSitesAssociationsCache.clientId, olm.clientId),
|
||||
eq(clientSitesAssociationsCache.siteId, site.siteId)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
Target,
|
||||
targets
|
||||
} from "@server/db";
|
||||
import { clients, clientSites, Newt, sites } from "@server/db";
|
||||
import { clients, clientSitesAssociationsCache, Newt, sites } from "@server/db";
|
||||
import { eq, and, inArray } from "drizzle-orm";
|
||||
import { updatePeer } from "../olm/peers";
|
||||
import { sendToExitNode } from "#dynamic/lib/exitNodes";
|
||||
@@ -138,8 +138,8 @@ export const handleGetConfigMessage: MessageHandler = async (context) => {
|
||||
const clientsRes = await db
|
||||
.select()
|
||||
.from(clients)
|
||||
.innerJoin(clientSites, eq(clients.clientId, clientSites.clientId))
|
||||
.where(eq(clientSites.siteId, siteId));
|
||||
.innerJoin(clientSitesAssociationsCache, eq(clients.clientId, clientSitesAssociationsCache.clientId))
|
||||
.where(eq(clientSitesAssociationsCache.siteId, siteId));
|
||||
|
||||
// Prepare peers data for the response
|
||||
const peers = await Promise.all(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import { db } from "@server/db";
|
||||
import { olms, clients, clientSites } from "@server/db";
|
||||
import { olms, clients, clientSitesAssociationsCache } from "@server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import createHttpError from "http-errors";
|
||||
@@ -57,8 +57,8 @@ export async function deleteUserOlm(
|
||||
// Delete client-site associations for each associated client
|
||||
for (const client of associatedClients) {
|
||||
await trx
|
||||
.delete(clientSites)
|
||||
.where(eq(clientSites.clientId, client.clientId));
|
||||
.delete(clientSitesAssociationsCache)
|
||||
.where(eq(clientSitesAssociationsCache.clientId, client.clientId));
|
||||
}
|
||||
|
||||
// Delete all associated clients
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
users
|
||||
} from "@server/db";
|
||||
import { MessageHandler } from "@server/routers/ws";
|
||||
import { clients, clientSites, exitNodes, Olm, olms, sites } from "@server/db";
|
||||
import { clients, clientSitesAssociationsCache, exitNodes, Olm, olms, sites } from "@server/db";
|
||||
import { and, eq, inArray, isNull } from "drizzle-orm";
|
||||
import { addPeer, deletePeer } from "../newt/peers";
|
||||
import logger from "@server/logger";
|
||||
@@ -159,19 +159,19 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => {
|
||||
|
||||
// set isRelay to false for all of the client's sites to reset the connection metadata
|
||||
await db
|
||||
.update(clientSites)
|
||||
.update(clientSitesAssociationsCache)
|
||||
.set({
|
||||
isRelayed: relay == true
|
||||
})
|
||||
.where(eq(clientSites.clientId, client.clientId));
|
||||
.where(eq(clientSitesAssociationsCache.clientId, client.clientId));
|
||||
}
|
||||
|
||||
// Get all sites data
|
||||
const sitesData = await db
|
||||
.select()
|
||||
.from(sites)
|
||||
.innerJoin(clientSites, eq(sites.siteId, clientSites.siteId))
|
||||
.where(eq(clientSites.clientId, client.clientId));
|
||||
.innerJoin(clientSitesAssociationsCache, eq(sites.siteId, clientSitesAssociationsCache.siteId))
|
||||
.where(eq(clientSitesAssociationsCache.clientId, client.clientId));
|
||||
|
||||
// Prepare an array to store site configurations
|
||||
const siteConfigurations = [];
|
||||
@@ -225,11 +225,11 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => {
|
||||
|
||||
const [clientSite] = await db
|
||||
.select()
|
||||
.from(clientSites)
|
||||
.from(clientSitesAssociationsCache)
|
||||
.where(
|
||||
and(
|
||||
eq(clientSites.clientId, client.clientId),
|
||||
eq(clientSites.siteId, site.siteId)
|
||||
eq(clientSitesAssociationsCache.clientId, client.clientId),
|
||||
eq(clientSitesAssociationsCache.siteId, site.siteId)
|
||||
)
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { db, exitNodes, sites } from "@server/db";
|
||||
import { MessageHandler } from "@server/routers/ws";
|
||||
import { clients, clientSites, Olm } from "@server/db";
|
||||
import { clients, clientSitesAssociationsCache, Olm } from "@server/db";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { updatePeer } from "../newt/peers";
|
||||
import logger from "@server/logger";
|
||||
@@ -67,14 +67,14 @@ export const handleOlmRelayMessage: MessageHandler = async (context) => {
|
||||
}
|
||||
|
||||
await db
|
||||
.update(clientSites)
|
||||
.update(clientSitesAssociationsCache)
|
||||
.set({
|
||||
isRelayed: true
|
||||
})
|
||||
.where(
|
||||
and(
|
||||
eq(clientSites.clientId, olm.clientId),
|
||||
eq(clientSites.siteId, siteId)
|
||||
eq(clientSitesAssociationsCache.clientId, olm.clientId),
|
||||
eq(clientSitesAssociationsCache.siteId, siteId)
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { rebuildSiteClientAssociations } from "@server/lib/rebuildSiteClientAssociations";
|
||||
import { rebuildClientAssociations } from "@server/lib/rebuildClientAssociations";
|
||||
|
||||
const addClientToSiteResourceBodySchema = z
|
||||
.object({
|
||||
@@ -136,7 +136,7 @@ export async function addClientToSiteResource(
|
||||
siteResourceId
|
||||
});
|
||||
|
||||
await rebuildSiteClientAssociations(siteResource, trx);
|
||||
await rebuildClientAssociations(siteResource, trx);
|
||||
});
|
||||
|
||||
return response(res, {
|
||||
|
||||
@@ -9,7 +9,7 @@ import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { rebuildSiteClientAssociations } from "@server/lib/rebuildSiteClientAssociations";
|
||||
import { rebuildClientAssociations } from "@server/lib/rebuildClientAssociations";
|
||||
|
||||
const addRoleToSiteResourceBodySchema = z
|
||||
.object({
|
||||
@@ -146,7 +146,7 @@ export async function addRoleToSiteResource(
|
||||
siteResourceId
|
||||
});
|
||||
|
||||
await rebuildSiteClientAssociations(siteResource, trx);
|
||||
await rebuildClientAssociations(siteResource, trx);
|
||||
});
|
||||
|
||||
return response(res, {
|
||||
|
||||
@@ -9,7 +9,7 @@ import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { rebuildSiteClientAssociations } from "@server/lib/rebuildSiteClientAssociations";
|
||||
import { rebuildClientAssociations } from "@server/lib/rebuildClientAssociations";
|
||||
|
||||
const addUserToSiteResourceBodySchema = z
|
||||
.object({
|
||||
@@ -115,7 +115,7 @@ export async function addUserToSiteResource(
|
||||
siteResourceId
|
||||
});
|
||||
|
||||
await rebuildSiteClientAssociations(siteResource, trx);
|
||||
await rebuildClientAssociations(siteResource, trx);
|
||||
});
|
||||
|
||||
return response(res, {
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { db, newts, roleResources, roles, roleSiteResources } from "@server/db";
|
||||
import { siteResources, sites, orgs, SiteResource } from "@server/db";
|
||||
import {
|
||||
clientSiteResources,
|
||||
db,
|
||||
newts,
|
||||
roles,
|
||||
roleSiteResources,
|
||||
userSiteResources
|
||||
} from "@server/db";
|
||||
import { siteResources, sites, SiteResource } from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import createHttpError from "http-errors";
|
||||
@@ -9,10 +16,8 @@ import { eq, and } from "drizzle-orm";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import logger from "@server/logger";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { addTargets } from "../client/targets";
|
||||
import { getUniqueSiteResourceName } from "@server/db/names";
|
||||
import { rebuildSiteClientAssociations } from "@server/lib/rebuildSiteClientAssociations";
|
||||
import { generateSubnetProxyTargets } from "@server/lib/ip";
|
||||
import { rebuildClientAssociations } from "@server/lib/rebuildClientAssociations";
|
||||
|
||||
const createSiteResourceParamsSchema = z.strictObject({
|
||||
siteId: z.string().transform(Number).pipe(z.int().positive()),
|
||||
@@ -23,12 +28,15 @@ const createSiteResourceSchema = z
|
||||
.strictObject({
|
||||
name: z.string().min(1).max(255),
|
||||
mode: z.enum(["host", "cidr", "port"]),
|
||||
protocol: z.enum(["tcp", "udp"]).optional(),
|
||||
// protocol: z.enum(["tcp", "udp"]).optional(),
|
||||
// proxyPort: z.int().positive().optional(),
|
||||
// destinationPort: z.int().positive().optional(),
|
||||
destination: z.string().min(1),
|
||||
enabled: z.boolean().default(true),
|
||||
alias: z.string().optional()
|
||||
alias: z.string().optional(),
|
||||
userIds: z.array(z.string()),
|
||||
roleIds: z.array(z.int()),
|
||||
clientIds: z.array(z.int())
|
||||
})
|
||||
.strict()
|
||||
// .refine(
|
||||
@@ -138,12 +146,15 @@ export async function createSiteResource(
|
||||
const {
|
||||
name,
|
||||
mode,
|
||||
protocol,
|
||||
// protocol,
|
||||
// proxyPort,
|
||||
// destinationPort,
|
||||
destination,
|
||||
enabled,
|
||||
alias
|
||||
alias,
|
||||
userIds,
|
||||
roleIds,
|
||||
clientIds
|
||||
} = parsedBody.data;
|
||||
|
||||
// Verify the site exists and belongs to the org
|
||||
@@ -194,7 +205,7 @@ export async function createSiteResource(
|
||||
orgId,
|
||||
name,
|
||||
mode,
|
||||
protocol: mode === "port" ? protocol : null,
|
||||
// protocol: mode === "port" ? protocol : null,
|
||||
// proxyPort: mode === "port" ? proxyPort : null,
|
||||
// destinationPort: mode === "port" ? destinationPort : null,
|
||||
destination,
|
||||
@@ -203,6 +214,10 @@ export async function createSiteResource(
|
||||
})
|
||||
.returning();
|
||||
|
||||
const siteResourceId = newSiteResource.siteResourceId;
|
||||
|
||||
//////////////////// update the associations ////////////////////
|
||||
|
||||
const [adminRole] = await trx
|
||||
.select()
|
||||
.from(roles)
|
||||
@@ -217,9 +232,34 @@ export async function createSiteResource(
|
||||
|
||||
await trx.insert(roleSiteResources).values({
|
||||
roleId: adminRole.roleId,
|
||||
siteResourceId: newSiteResource.siteResourceId
|
||||
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
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
const [newt] = await trx
|
||||
.select()
|
||||
.from(newts)
|
||||
@@ -232,10 +272,10 @@ export async function createSiteResource(
|
||||
);
|
||||
}
|
||||
|
||||
const targets = await generateSubnetProxyTargets([newSiteResource], trx);
|
||||
await addTargets(newt.newtId, targets);
|
||||
// const targets = await generateSubnetProxyTargets([newSiteResource], trx);
|
||||
// await addTargets(newt.newtId, targets);
|
||||
|
||||
await rebuildSiteClientAssociations(newSiteResource, trx); // we need to call this because we added to the admin role
|
||||
await rebuildClientAssociations(newSiteResource, trx); // we need to call this because we added to the admin role
|
||||
});
|
||||
|
||||
if (!newSiteResource) {
|
||||
|
||||
@@ -9,9 +9,7 @@ import { eq, and } from "drizzle-orm";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import logger from "@server/logger";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { removeTargets } from "../client/targets";
|
||||
import { rebuildSiteClientAssociations } from "@server/lib/rebuildSiteClientAssociations";
|
||||
import { generateSubnetProxyTargets } from "@server/lib/ip";
|
||||
import { rebuildClientAssociations } from "@server/lib/rebuildClientAssociations";
|
||||
|
||||
const deleteSiteResourceParamsSchema = z.strictObject({
|
||||
siteResourceId: z.string().transform(Number).pipe(z.int().positive()),
|
||||
@@ -108,10 +106,10 @@ export async function deleteSiteResource(
|
||||
);
|
||||
}
|
||||
|
||||
const targets = await generateSubnetProxyTargets([removedSiteResource], trx);
|
||||
await removeTargets(newt.newtId, targets);
|
||||
// const targets = await generateSubnetProxyTargets([removedSiteResource], trx);
|
||||
// await removeTargets(newt.newtId, targets);
|
||||
|
||||
await rebuildSiteClientAssociations(existingSiteResource, trx);
|
||||
await rebuildClientAssociations(existingSiteResource, trx);
|
||||
});
|
||||
|
||||
logger.info(
|
||||
|
||||
@@ -8,7 +8,7 @@ import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { rebuildSiteClientAssociations } from "@server/lib/rebuildSiteClientAssociations";
|
||||
import { rebuildClientAssociations } from "@server/lib/rebuildClientAssociations";
|
||||
|
||||
const removeClientFromSiteResourceBodySchema = z
|
||||
.object({
|
||||
@@ -142,7 +142,7 @@ export async function removeClientFromSiteResource(
|
||||
)
|
||||
);
|
||||
|
||||
await rebuildSiteClientAssociations(siteResource, trx);
|
||||
await rebuildClientAssociations(siteResource, trx);
|
||||
});
|
||||
|
||||
return response(res, {
|
||||
|
||||
@@ -9,7 +9,7 @@ import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { rebuildSiteClientAssociations } from "@server/lib/rebuildSiteClientAssociations";
|
||||
import { rebuildClientAssociations } from "@server/lib/rebuildClientAssociations";
|
||||
|
||||
const removeRoleFromSiteResourceBodySchema = z
|
||||
.object({
|
||||
@@ -151,7 +151,7 @@ export async function removeRoleFromSiteResource(
|
||||
)
|
||||
);
|
||||
|
||||
await rebuildSiteClientAssociations(siteResource, trx);
|
||||
await rebuildClientAssociations(siteResource, trx);
|
||||
});
|
||||
|
||||
return response(res, {
|
||||
|
||||
@@ -9,7 +9,7 @@ import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { rebuildSiteClientAssociations } from "@server/lib/rebuildSiteClientAssociations";
|
||||
import { rebuildClientAssociations } from "@server/lib/rebuildClientAssociations";
|
||||
|
||||
const removeUserFromSiteResourceBodySchema = z
|
||||
.object({
|
||||
@@ -121,7 +121,7 @@ export async function removeUserFromSiteResource(
|
||||
)
|
||||
);
|
||||
|
||||
await rebuildSiteClientAssociations(siteResource, trx);
|
||||
await rebuildClientAssociations(siteResource, trx);
|
||||
});
|
||||
|
||||
return response(res, {
|
||||
|
||||
@@ -8,7 +8,7 @@ import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { eq, inArray } from "drizzle-orm";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { rebuildSiteClientAssociations } from "@server/lib/rebuildSiteClientAssociations";
|
||||
import { rebuildClientAssociations } from "@server/lib/rebuildClientAssociations";
|
||||
|
||||
const setSiteResourceClientsBodySchema = z
|
||||
.object({
|
||||
@@ -119,17 +119,12 @@ export async function setSiteResourceClients(
|
||||
.where(eq(clientSiteResources.siteResourceId, siteResourceId));
|
||||
|
||||
if (clientIds.length > 0) {
|
||||
await Promise.all(
|
||||
clientIds.map((clientId) =>
|
||||
trx
|
||||
.insert(clientSiteResources)
|
||||
.values({ clientId, siteResourceId })
|
||||
.returning()
|
||||
)
|
||||
);
|
||||
await trx
|
||||
.insert(clientSiteResources)
|
||||
.values(clientIds.map((clientId) => ({ clientId, siteResourceId })));
|
||||
}
|
||||
|
||||
await rebuildSiteClientAssociations(siteResource, trx);
|
||||
await rebuildClientAssociations(siteResource, trx);
|
||||
});
|
||||
|
||||
return response(res, {
|
||||
|
||||
@@ -9,7 +9,7 @@ import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { eq, and, ne, inArray } from "drizzle-orm";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { rebuildSiteClientAssociations } from "@server/lib/rebuildSiteClientAssociations";
|
||||
import { rebuildClientAssociations } from "@server/lib/rebuildClientAssociations";
|
||||
|
||||
const setSiteResourceRolesBodySchema = z
|
||||
.object({
|
||||
@@ -141,16 +141,13 @@ export async function setSiteResourceRoles(
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
roleIds.map((roleId) =>
|
||||
trx
|
||||
.insert(roleSiteResources)
|
||||
.values({ roleId, siteResourceId })
|
||||
.returning()
|
||||
)
|
||||
);
|
||||
if (roleIds.length > 0) {
|
||||
await trx
|
||||
.insert(roleSiteResources)
|
||||
.values(roleIds.map((roleId) => ({ roleId, siteResourceId })));
|
||||
}
|
||||
|
||||
await rebuildSiteClientAssociations(siteResource, trx);
|
||||
await rebuildClientAssociations(siteResource, trx);
|
||||
});
|
||||
|
||||
return response(res, {
|
||||
|
||||
@@ -9,7 +9,7 @@ import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { rebuildSiteClientAssociations } from "@server/lib/rebuildSiteClientAssociations";
|
||||
import { rebuildClientAssociations } from "@server/lib/rebuildClientAssociations";
|
||||
|
||||
const setSiteResourceUsersBodySchema = z
|
||||
.object({
|
||||
@@ -96,16 +96,13 @@ export async function setSiteResourceUsers(
|
||||
.delete(userSiteResources)
|
||||
.where(eq(userSiteResources.siteResourceId, siteResourceId));
|
||||
|
||||
await Promise.all(
|
||||
userIds.map((userId) =>
|
||||
trx
|
||||
.insert(userSiteResources)
|
||||
.values({ userId, siteResourceId })
|
||||
.returning()
|
||||
)
|
||||
);
|
||||
if (userIds.length > 0) {
|
||||
await trx
|
||||
.insert(userSiteResources)
|
||||
.values(userIds.map((userId) => ({ userId, siteResourceId })));
|
||||
}
|
||||
|
||||
await rebuildSiteClientAssociations(siteResource, trx);
|
||||
await rebuildClientAssociations(siteResource, trx);
|
||||
});
|
||||
|
||||
return response(res, {
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { db, newts, sites } from "@server/db";
|
||||
import {
|
||||
clientSiteResources,
|
||||
db,
|
||||
newts,
|
||||
roles,
|
||||
roleSiteResources,
|
||||
sites,
|
||||
userSiteResources
|
||||
} from "@server/db";
|
||||
import { siteResources, SiteResource } from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import createHttpError from "http-errors";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { eq, and, ne } from "drizzle-orm";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import logger from "@server/logger";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { updateTargets } from "@server/routers/client/targets";
|
||||
import { generateSubnetProxyTargets } from "@server/lib/ip";
|
||||
import { generateSingleSubnetProxyTargets } from "@server/lib/ip";
|
||||
import {
|
||||
getClientSiteResourceAccess,
|
||||
rebuildClientAssociations
|
||||
} from "@server/lib/rebuildClientAssociations";
|
||||
|
||||
const updateSiteResourceParamsSchema = z.strictObject({
|
||||
siteResourceId: z.string().transform(Number).pipe(z.int().positive()),
|
||||
@@ -23,12 +35,15 @@ const updateSiteResourceSchema = z
|
||||
name: z.string().min(1).max(255).optional(),
|
||||
// mode: z.enum(["host", "cidr", "port"]).optional(),
|
||||
mode: z.enum(["host", "cidr"]).optional(),
|
||||
protocol: z.enum(["tcp", "udp"]).nullish(),
|
||||
// protocol: z.enum(["tcp", "udp"]).nullish(),
|
||||
// proxyPort: z.int().positive().nullish(),
|
||||
// destinationPort: z.int().positive().nullish(),
|
||||
destination: z.string().min(1).optional(),
|
||||
enabled: z.boolean().optional(),
|
||||
alias: z.string().nullish()
|
||||
alias: z.string().nullish(),
|
||||
userIds: z.array(z.string()),
|
||||
roleIds: z.array(z.int()),
|
||||
clientIds: z.array(z.int())
|
||||
})
|
||||
.strict();
|
||||
|
||||
@@ -82,7 +97,16 @@ export async function updateSiteResource(
|
||||
}
|
||||
|
||||
const { siteResourceId, siteId, orgId } = parsedParams.data;
|
||||
const updateData = parsedBody.data;
|
||||
const {
|
||||
name,
|
||||
mode,
|
||||
destination,
|
||||
alias,
|
||||
enabled,
|
||||
userIds,
|
||||
roleIds,
|
||||
clientIds
|
||||
} = parsedBody.data;
|
||||
|
||||
const [site] = await db
|
||||
.select()
|
||||
@@ -113,85 +137,131 @@ export async function updateSiteResource(
|
||||
);
|
||||
}
|
||||
|
||||
// Determine the final mode and validate port mode requirements
|
||||
const finalMode = updateData.mode || existingSiteResource.mode;
|
||||
const finalProtocol = updateData.protocol !== undefined ? updateData.protocol : existingSiteResource.protocol;
|
||||
// const finalProxyPort = updateData.proxyPort !== undefined ? updateData.proxyPort : existingSiteResource.proxyPort;
|
||||
// const finalDestinationPort = updateData.destinationPort !== undefined ? updateData.destinationPort : existingSiteResource.destinationPort;
|
||||
|
||||
// Prepare update data
|
||||
const updateValues: any = {};
|
||||
if (updateData.name !== undefined) updateValues.name = updateData.name;
|
||||
if (updateData.mode !== undefined) updateValues.mode = updateData.mode;
|
||||
if (updateData.destination !== undefined)
|
||||
updateValues.destination = updateData.destination;
|
||||
if (updateData.enabled !== undefined)
|
||||
updateValues.enabled = updateData.enabled;
|
||||
|
||||
// Handle nullish fields (can be undefined, null, or a value)
|
||||
if (updateData.alias !== undefined) {
|
||||
updateValues.alias =
|
||||
updateData.alias && updateData.alias.trim()
|
||||
? updateData.alias
|
||||
: null;
|
||||
}
|
||||
|
||||
// Handle port mode fields - include in update if explicitly provided (null or value) or if mode changed
|
||||
// const isModeChangingFromPort =
|
||||
// existingSiteResource.mode === "port" &&
|
||||
// updateData.mode &&
|
||||
// updateData.mode !== "port";
|
||||
|
||||
// if (updateData.protocol !== undefined || isModeChangingFromPort) {
|
||||
// updateValues.protocol = finalMode === "port" ? finalProtocol : null;
|
||||
// }
|
||||
// if (updateData.proxyPort !== undefined || isModeChangingFromPort) {
|
||||
// updateValues.proxyPort =
|
||||
// finalMode === "port" ? finalProxyPort : null;
|
||||
// }
|
||||
// if (
|
||||
// updateData.destinationPort !== undefined ||
|
||||
// isModeChangingFromPort
|
||||
// ) {
|
||||
// updateValues.destinationPort =
|
||||
// finalMode === "port" ? finalDestinationPort : null;
|
||||
// }
|
||||
|
||||
// Update the site resource
|
||||
const [updatedSiteResource] = await db
|
||||
.update(siteResources)
|
||||
.set(updateValues)
|
||||
.where(
|
||||
and(
|
||||
eq(siteResources.siteResourceId, siteResourceId),
|
||||
eq(siteResources.siteId, siteId),
|
||||
eq(siteResources.orgId, orgId)
|
||||
let updatedSiteResource: SiteResource | undefined;
|
||||
await db.transaction(async (trx) => {
|
||||
// Update the site resource
|
||||
[updatedSiteResource] = await trx
|
||||
.update(siteResources)
|
||||
.set({
|
||||
name: name,
|
||||
mode: mode,
|
||||
destination: destination,
|
||||
enabled: enabled,
|
||||
alias: alias && alias.trim() ? alias : null
|
||||
})
|
||||
.where(
|
||||
and(
|
||||
eq(siteResources.siteResourceId, siteResourceId),
|
||||
eq(siteResources.siteId, siteId),
|
||||
eq(siteResources.orgId, orgId)
|
||||
)
|
||||
)
|
||||
)
|
||||
.returning();
|
||||
.returning();
|
||||
|
||||
const [newt] = await db
|
||||
.select()
|
||||
.from(newts)
|
||||
.where(eq(newts.siteId, site.siteId))
|
||||
.limit(1);
|
||||
//////////////////// update the associations ////////////////////
|
||||
|
||||
if (!newt) {
|
||||
return next(createHttpError(HttpCode.NOT_FOUND, "Newt not found"));
|
||||
}
|
||||
await trx
|
||||
.delete(clientSiteResources)
|
||||
.where(eq(clientSiteResources.siteResourceId, siteResourceId));
|
||||
|
||||
const oldTargets = await generateSubnetProxyTargets([existingSiteResource]);
|
||||
const newTargets = await generateSubnetProxyTargets([updatedSiteResource]);
|
||||
if (clientIds.length > 0) {
|
||||
await trx.insert(clientSiteResources).values(
|
||||
clientIds.map((clientId) => ({
|
||||
clientId,
|
||||
siteResourceId
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
await updateTargets(newt.newtId, {
|
||||
oldTargets: oldTargets,
|
||||
newTargets: newTargets
|
||||
await trx
|
||||
.delete(userSiteResources)
|
||||
.where(eq(userSiteResources.siteResourceId, siteResourceId));
|
||||
|
||||
if (userIds.length > 0) {
|
||||
await trx
|
||||
.insert(userSiteResources)
|
||||
.values(
|
||||
userIds.map((userId) => ({ userId, siteResourceId }))
|
||||
);
|
||||
}
|
||||
|
||||
// Get all admin role IDs for this org to exclude from deletion
|
||||
const adminRoles = await trx
|
||||
.select()
|
||||
.from(roles)
|
||||
.where(
|
||||
and(
|
||||
eq(roles.isAdmin, true),
|
||||
eq(roles.orgId, updatedSiteResource.orgId)
|
||||
)
|
||||
);
|
||||
const adminRoleIds = adminRoles.map((role) => role.roleId);
|
||||
|
||||
if (adminRoleIds.length > 0) {
|
||||
await trx.delete(roleSiteResources).where(
|
||||
and(
|
||||
eq(roleSiteResources.siteResourceId, siteResourceId),
|
||||
ne(roleSiteResources.roleId, adminRoleIds[0]) // delete all but the admin role
|
||||
)
|
||||
);
|
||||
} else {
|
||||
await trx
|
||||
.delete(roleSiteResources)
|
||||
.where(
|
||||
eq(roleSiteResources.siteResourceId, siteResourceId)
|
||||
);
|
||||
}
|
||||
|
||||
if (roleIds.length > 0) {
|
||||
await trx
|
||||
.insert(roleSiteResources)
|
||||
.values(
|
||||
roleIds.map((roleId) => ({ roleId, siteResourceId }))
|
||||
);
|
||||
}
|
||||
|
||||
const { mergedAllClients } = await rebuildClientAssociations(
|
||||
updatedSiteResource,
|
||||
trx
|
||||
); // we need to call this because we added to the admin role
|
||||
|
||||
// after everything is rebuilt above we still need to update the targets if the destination changed
|
||||
if (
|
||||
existingSiteResource.destination !==
|
||||
updatedSiteResource.destination
|
||||
) {
|
||||
const [newt] = await trx
|
||||
.select()
|
||||
.from(newts)
|
||||
.where(eq(newts.siteId, site.siteId))
|
||||
.limit(1);
|
||||
|
||||
if (!newt) {
|
||||
return next(
|
||||
createHttpError(HttpCode.NOT_FOUND, "Newt not found")
|
||||
);
|
||||
}
|
||||
|
||||
const oldTargets = generateSingleSubnetProxyTargets(
|
||||
existingSiteResource,
|
||||
mergedAllClients
|
||||
);
|
||||
const newTargets = generateSingleSubnetProxyTargets(
|
||||
updatedSiteResource,
|
||||
mergedAllClients
|
||||
);
|
||||
|
||||
await updateTargets(newt.newtId, {
|
||||
oldTargets: oldTargets,
|
||||
newTargets: newTargets
|
||||
});
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Updated site resource ${siteResourceId} for site ${siteId}`
|
||||
);
|
||||
});
|
||||
|
||||
logger.info(
|
||||
`Updated site resource ${siteResourceId} for site ${siteId}`
|
||||
);
|
||||
|
||||
return response(res, {
|
||||
data: updatedSiteResource,
|
||||
success: true,
|
||||
|
||||
Reference in New Issue
Block a user