diff --git a/server/routers/gerbil/getConfig.ts b/server/routers/gerbil/getConfig.ts index 314e715a..28b576d8 100644 --- a/server/routers/gerbil/getConfig.ts +++ b/server/routers/gerbil/getConfig.ts @@ -11,6 +11,7 @@ import config from "@server/lib/config"; import { getUniqueExitNodeEndpointName } from '@server/db/names'; import { findNextAvailableCidr } from "@server/lib/ip"; import { fromError } from 'zod-validation-error'; +import { getAllowedIps } from '../target/helpers'; // Define Zod schema for request validation const getConfigSchema = z.object({ publicKey: z.string(), @@ -83,22 +84,9 @@ export async function getConfig(req: Request, res: Response, next: NextFunction) }); const peers = await Promise.all(sitesRes.map(async (site) => { - // Fetch resources for this site - const resourcesRes = await db.query.resources.findMany({ - where: eq(resources.siteId, site.siteId), - }); - - // Fetch targets for all resources of this site - const targetIps = await Promise.all(resourcesRes.map(async (resource) => { - const targetsRes = await db.query.targets.findMany({ - where: eq(targets.resourceId, resource.resourceId), - }); - return targetsRes.map(target => `${target.ip}/32`); - })); - return { publicKey: site.pubKey, - allowedIps: targetIps.flat(), + allowedIps: await getAllowedIps(site.siteId) }; })); diff --git a/server/routers/newt/targets.ts b/server/routers/newt/targets.ts index e5f7855c..2c1143e6 100644 --- a/server/routers/newt/targets.ts +++ b/server/routers/newt/targets.ts @@ -1,11 +1,11 @@ import { Target } from "@server/db/schema"; import { sendToClient } from "../ws"; -export async function addTargets( +export function addTargets( newtId: string, targets: Target[], protocol: string -): Promise { +) { //create a list of udp and tcp targets const payloadTargets = targets.map((target) => { return `${target.internalPort ? target.internalPort + ":" : ""}${ @@ -22,11 +22,11 @@ export async function addTargets( sendToClient(newtId, payload); } -export async function removeTargets( +export function removeTargets( newtId: string, targets: Target[], protocol: string -): Promise { +) { //create a list of udp and tcp targets const payloadTargets = targets.map((target) => { return `${target.internalPort ? target.internalPort + ":" : ""}${ diff --git a/server/routers/resource/deleteResource.ts b/server/routers/resource/deleteResource.ts index ed0fc95f..8acf0d77 100644 --- a/server/routers/resource/deleteResource.ts +++ b/server/routers/resource/deleteResource.ts @@ -10,6 +10,7 @@ import logger from "@server/logger"; import { fromError } from "zod-validation-error"; import { addPeer } from "../gerbil/peers"; import { removeTargets } from "../newt/targets"; +import { getAllowedIps } from "../target/helpers"; // Define Zod schema for request parameters validation const deleteResourceSchema = z @@ -75,25 +76,9 @@ export async function deleteResource( if (site.pubKey) { if (site.type == "wireguard") { - // TODO: is this all inefficient? - // Fetch resources for this site - const resourcesRes = await db.query.resources.findMany({ - where: eq(resources.siteId, site.siteId) - }); - - // Fetch targets for all resources of this site - const targetIps = await Promise.all( - resourcesRes.map(async (resource) => { - const targetsRes = await db.query.targets.findMany({ - where: eq(targets.resourceId, resource.resourceId) - }); - return targetsRes.map((target) => `${target.ip}/32`); - }) - ); - await addPeer(site.exitNodeId!, { publicKey: site.pubKey, - allowedIps: targetIps.flat() + allowedIps: await getAllowedIps(site.siteId) }); } else if (site.type == "newt") { // get the newt on the site by querying the newt table for siteId diff --git a/server/routers/resource/transferResource.ts b/server/routers/resource/transferResource.ts index 91cb9774..31777c30 100644 --- a/server/routers/resource/transferResource.ts +++ b/server/routers/resource/transferResource.ts @@ -1,13 +1,16 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import { db } from "@server/db"; -import { resources } from "@server/db/schema"; +import { newts, resources, sites, targets } from "@server/db/schema"; import { eq } from "drizzle-orm"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; import logger from "@server/logger"; import { fromError } from "zod-validation-error"; +import { addPeer } from "../gerbil/peers"; +import { addTargets, removeTargets } from "../newt/targets"; +import { getAllowedIps } from "../target/helpers"; const transferResourceParamsSchema = z .object({ @@ -53,6 +56,60 @@ export async function transferResource( const { resourceId } = parsedParams.data; const { siteId } = parsedBody.data; + const [oldResource] = await db + .select() + .from(resources) + .where(eq(resources.resourceId, resourceId)) + .limit(1); + + if (!oldResource) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Resource with ID ${resourceId} not found` + ) + ); + } + + if (oldResource.siteId === siteId) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + `Resource is already assigned to site with ID ${siteId}` + ) + ); + } + + const [newSite] = await db + .select() + .from(sites) + .where(eq(sites.siteId, siteId)) + .limit(1); + + if (!newSite) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Site with ID ${siteId} not found` + ) + ); + } + + const [oldSite] = await db + .select() + .from(sites) + .where(eq(sites.siteId, oldResource.siteId)) + .limit(1); + + if (!oldSite) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Site with ID ${oldResource.siteId} not found` + ) + ); + } + const [updatedResource] = await db .update(resources) .set({ siteId }) @@ -68,6 +125,57 @@ export async function transferResource( ); } + const resourceTargets = await db + .select() + .from(targets) + .where(eq(targets.resourceId, resourceId)); + + if (resourceTargets.length > 0) { + ////// REMOVE THE TARGETS FROM THE OLD SITE ////// + if (oldSite.pubKey) { + if (oldSite.type == "wireguard") { + await addPeer(oldSite.exitNodeId!, { + publicKey: oldSite.pubKey, + allowedIps: await getAllowedIps(oldSite.siteId) + }); + } else if (oldSite.type == "newt") { + const [newt] = await db + .select() + .from(newts) + .where(eq(newts.siteId, oldSite.siteId)) + .limit(1); + + removeTargets( + newt.newtId, + resourceTargets, + updatedResource.protocol + ); + } + } + + ////// ADD THE TARGETS TO THE NEW SITE ////// + if (newSite.pubKey) { + if (newSite.type == "wireguard") { + await addPeer(newSite.exitNodeId!, { + publicKey: newSite.pubKey, + allowedIps: await getAllowedIps(newSite.siteId) + }); + } else if (newSite.type == "newt") { + const [newt] = await db + .select() + .from(newts) + .where(eq(newts.siteId, newSite.siteId)) + .limit(1); + + addTargets( + newt.newtId, + resourceTargets, + updatedResource.protocol + ); + } + } + } + return response(res, { data: updatedResource, success: true, diff --git a/server/routers/target/createTarget.ts b/server/routers/target/createTarget.ts index 3d5e8d0e..b1080d87 100644 --- a/server/routers/target/createTarget.ts +++ b/server/routers/target/createTarget.ts @@ -11,7 +11,7 @@ import { isIpInCidr } from "@server/lib/ip"; import { fromError } from "zod-validation-error"; import { addTargets } from "../newt/targets"; import { eq } from "drizzle-orm"; -import { pickPort } from "./ports"; +import { pickPort } from "./helpers"; // Regular expressions for validation const DOMAIN_REGEX = diff --git a/server/routers/target/deleteTarget.ts b/server/routers/target/deleteTarget.ts index 97dab71c..7472b73d 100644 --- a/server/routers/target/deleteTarget.ts +++ b/server/routers/target/deleteTarget.ts @@ -10,6 +10,7 @@ import logger from "@server/logger"; import { addPeer } from "../gerbil/peers"; import { fromError } from "zod-validation-error"; import { removeTargets } from "../newt/targets"; +import { getAllowedIps } from "./helpers"; const deleteTargetSchema = z .object({ @@ -80,25 +81,9 @@ export async function deleteTarget( if (site.pubKey) { if (site.type == "wireguard") { - // TODO: is this all inefficient? - // Fetch resources for this site - const resourcesRes = await db.query.resources.findMany({ - where: eq(resources.siteId, site.siteId) - }); - - // Fetch targets for all resources of this site - const targetIps = await Promise.all( - resourcesRes.map(async (resource) => { - const targetsRes = await db.query.targets.findMany({ - where: eq(targets.resourceId, resource.resourceId) - }); - return targetsRes.map((target) => `${target.ip}/32`); - }) - ); - await addPeer(site.exitNodeId!, { publicKey: site.pubKey, - allowedIps: targetIps.flat() + allowedIps: await getAllowedIps(site.siteId) }); } else if (site.type == "newt") { // get the newt on the site by querying the newt table for siteId diff --git a/server/routers/target/ports.ts b/server/routers/target/helpers.ts similarity index 71% rename from server/routers/target/ports.ts rename to server/routers/target/helpers.ts index bfa8f280..606e2290 100644 --- a/server/routers/target/ports.ts +++ b/server/routers/target/helpers.ts @@ -46,3 +46,21 @@ export async function pickPort(siteId: number): Promise<{ return { internalPort, targetIps }; } + +export async function getAllowedIps(siteId: number) { + // TODO: is this all inefficient? + const resourcesRes = await db.query.resources.findMany({ + where: eq(resources.siteId, siteId) + }); + + // Fetch targets for all resources of this site + const targetIps = await Promise.all( + resourcesRes.map(async (resource) => { + const targetsRes = await db.query.targets.findMany({ + where: eq(targets.resourceId, resource.resourceId) + }); + return targetsRes.map((target) => `${target.ip}/32`); + }) + ); + return targetIps.flat(); +} diff --git a/server/routers/target/updateTarget.ts b/server/routers/target/updateTarget.ts index 4125fd9c..2ae6222d 100644 --- a/server/routers/target/updateTarget.ts +++ b/server/routers/target/updateTarget.ts @@ -10,7 +10,7 @@ import logger from "@server/logger"; import { fromError } from "zod-validation-error"; import { addPeer } from "../gerbil/peers"; import { addTargets } from "../newt/targets"; -import { pickPort } from "./ports"; +import { pickPort } from "./helpers"; // Regular expressions for validation const DOMAIN_REGEX = diff --git a/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx index bfcaa134..b4b99eef 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx @@ -19,7 +19,7 @@ import { CommandEmpty, CommandGroup, CommandInput, - CommandItem, + CommandItem } from "@/components/ui/command"; import { cn } from "@app/lib/cn"; import { @@ -144,14 +144,15 @@ export default function GeneralForm() { async function onSubmit(data: GeneralFormValues) { setSaveLoading(true); - api.post>( - `resource/${resource?.resourceId}`, - { - name: data.name, - subdomain: data.subdomain - // siteId: data.siteId, - } - ) + const res = await api + .post>( + `resource/${resource?.resourceId}`, + { + name: data.name, + subdomain: data.subdomain + // siteId: data.siteId, + } + ) .catch((e) => { toast({ variant: "destructive", @@ -161,26 +162,26 @@ export default function GeneralForm() { "An error occurred while updating the resource" ) }); - }) - .then(() => { - toast({ - title: "Resource updated", - description: "The resource has been updated successfully" - }); + }); - updateResource({ name: data.name, subdomain: data.subdomain }); + if (res && res.status === 200) { + toast({ + title: "Resource updated", + description: "The resource has been updated successfully" + }); - router.refresh(); - }) - .finally(() => setSaveLoading(false)); + updateResource({ name: data.name, subdomain: data.subdomain }); + } + setSaveLoading(false); } async function onTransfer(data: TransferFormValues) { setTransferLoading(true); - api.post(`resource/${resource?.resourceId}/transfer`, { - siteId: data.siteId - }) + const res = await api + .post(`resource/${resource?.resourceId}/transfer`, { + siteId: data.siteId + }) .catch((e) => { toast({ variant: "destructive", @@ -190,16 +191,16 @@ export default function GeneralForm() { "An error occurred while transferring the resource" ) }); - }) - .then(() => { - toast({ - title: "Resource transferred", - description: - "The resource has been transferred successfully" - }); - router.refresh(); - }) - .finally(() => setTransferLoading(false)); + }); + + if (res && res.status === 200) { + toast({ + title: "Resource transferred", + description: "The resource has been transferred successfully" + }); + router.refresh(); + } + setTransferLoading(false); } return (