Working on delete

This commit is contained in:
Owen Schwartz 2024-12-24 12:08:31 -05:00
parent 8e544cca42
commit 35358cbe57
No known key found for this signature in database
GPG key ID: 8271FDFFD9E0CCBD
5 changed files with 113 additions and 24 deletions

View file

@ -27,7 +27,6 @@ export async function verifyUserIsOrgOwner(
) )
); );
} }
try { try {
if (!req.userOrg) { if (!req.userOrg) {
const res = await db const res = await db
@ -56,6 +55,8 @@ export async function verifyUserIsOrgOwner(
) )
); );
} }
return next();
} catch (e) { } catch (e) {
return next( return next(
createHttpError( createHttpError(

View file

@ -24,6 +24,10 @@ const deleteOrgSchema = z
}) })
.strict(); .strict();
export type DeleteOrgResponse = {
}
export async function deleteOrg( export async function deleteOrg(
req: Request, req: Request,
res: Response, res: Response,
@ -41,7 +45,6 @@ export async function deleteOrg(
} }
const { orgId } = parsedParams.data; const { orgId } = parsedParams.data;
// Check if the user has permission to list sites // Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission( const hasPermission = await checkUserActionPermission(
ActionsEnum.deleteOrg, ActionsEnum.deleteOrg,
@ -55,7 +58,6 @@ export async function deleteOrg(
) )
); );
} }
const [org] = await db const [org] = await db
.select() .select()
.from(orgs) .from(orgs)
@ -70,7 +72,6 @@ export async function deleteOrg(
) )
); );
} }
// we need to handle deleting each site // we need to handle deleting each site
const orgSites = await db const orgSites = await db
.select() .select()
@ -97,20 +98,20 @@ export async function deleteOrg(
sendToClient(deletedNewt.newtId, payload); sendToClient(deletedNewt.newtId, payload);
// delete all of the sessions for the newt // delete all of the sessions for the newt
db.delete(newtSessions) await db.delete(newtSessions)
.where( .where(
eq(newtSessions.newtId, deletedNewt.newtId) eq(newtSessions.newtId, deletedNewt.newtId)
) );
.run();
} }
} }
} }
db.delete(sites).where(eq(sites.siteId, site.siteId)).run(); logger.info(`Deleting site ${site.siteId}`);
await db.delete(sites).where(eq(sites.siteId, site.siteId))
} }
} }
await db.delete(orgs).where(eq(orgs.orgId, orgId)).returning(); await db.delete(orgs).where(eq(orgs.orgId, orgId));
return response(res, { return response(res, {
data: null, data: null,

View file

@ -28,6 +28,7 @@ export type GetSiteResponse = {
name: string; name: string;
subdomain: string; subdomain: string;
subnet: string; subnet: string;
type: string;
}; };
export async function getSite( export async function getSite(
@ -81,7 +82,8 @@ export async function getSite(
siteId: site[0].siteId, siteId: site[0].siteId,
niceId: site[0].niceId, niceId: site[0].niceId,
name: site[0].name, name: site[0].name,
subnet: site[0].subnet subnet: site[0].subnet,
type: site[0].type
}, },
success: true, success: true,
error: false, error: false,

View file

@ -30,6 +30,8 @@ import {
CardHeader, CardHeader,
CardTitle CardTitle
} from "@/components/ui/card"; } from "@/components/ui/card";
import { AxiosResponse } from "axios";
import { DeleteOrgResponse } from "@server/routers/org";
const GeneralFormSchema = z.object({ const GeneralFormSchema = z.object({
name: z.string() name: z.string()
@ -54,18 +56,25 @@ export default function GeneralPage() {
}); });
async function deleteOrg() { async function deleteOrg() {
await api try {
.delete(`/org/${org?.org.orgId}`)
.catch((e) => { const res = await api
.delete<AxiosResponse<DeleteOrgResponse>>(`/org/${org?.org.orgId}`);
if (res.status === 200) {
console.log("Org deleted");
}
} catch (err) {
console.error(err);
toast({ toast({
variant: "destructive", variant: "destructive",
title: "Failed to delete org", title: "Failed to delete org",
description: formatAxiosError( description: formatAxiosError(
e, err,
"An error occurred while deleting the org." "An error occurred while deleting the org."
), ),
}); });
}); }
} }
async function onSubmit(data: GeneralFormValues) { async function onSubmit(data: GeneralFormValues) {

View file

@ -51,6 +51,7 @@ import { ArrayElement } from "@server/types/ArrayElement";
import { formatAxiosError } from "@app/lib/utils"; import { formatAxiosError } from "@app/lib/utils";
import { useEnvContext } from "@app/hooks/useEnvContext"; import { useEnvContext } from "@app/hooks/useEnvContext";
import { createApiClient } from "@app/api"; import { createApiClient } from "@app/api";
import { GetSiteResponse } from "@server/routers/site";
const addTargetSchema = z.object({ const addTargetSchema = z.object({
ip: z.string().ip(), ip: z.string().ip(),
@ -85,6 +86,7 @@ export default function ReverseProxyTargets(props: {
const api = createApiClient(useEnvContext()); const api = createApiClient(useEnvContext());
const [targets, setTargets] = useState<LocalTarget[]>([]); const [targets, setTargets] = useState<LocalTarget[]>([]);
const [site, setSite] = useState<GetSiteResponse>();
const [targetsToRemove, setTargetsToRemove] = useState<number[]>([]); const [targetsToRemove, setTargetsToRemove] = useState<number[]>([]);
const [sslEnabled, setSslEnabled] = useState(resource.ssl); const [sslEnabled, setSslEnabled] = useState(resource.ssl);
@ -103,7 +105,7 @@ export default function ReverseProxyTargets(props: {
}); });
useEffect(() => { useEffect(() => {
const fetchSites = async () => { const fetchTargets = async () => {
try { try {
const res = await api.get<AxiosResponse<ListTargetsResponse>>( const res = await api.get<AxiosResponse<ListTargetsResponse>>(
`/resource/${params.resourceId}/targets`, `/resource/${params.resourceId}/targets`,
@ -126,7 +128,30 @@ export default function ReverseProxyTargets(props: {
setPageLoading(false); setPageLoading(false);
} }
}; };
fetchSites(); fetchTargets();
const fetchSite = async () => {
try {
const res = await api.get<AxiosResponse<GetSiteResponse>>(
`/site/${resource.siteId}`,
);
if (res.status === 200) {
setSite(res.data.data);
}
} catch (err) {
console.error(err);
toast({
variant: "destructive",
title: "Failed to fetch resource",
description: formatAxiosError(
err,
"An error occurred while fetching resource",
),
});
}
}
fetchSite();
}, []); }, []);
async function addTarget(data: AddTargetFormValues) { async function addTarget(data: AddTargetFormValues) {
@ -146,6 +171,20 @@ export default function ReverseProxyTargets(props: {
return; return;
} }
if (site && site.type == "wireguard" && site.subnet) {
// make sure that the target IP is within the site subnet
const targetIp = data.ip;
const subnet = site.subnet;
if (!isIPInSubnet(targetIp, subnet)) {
toast({
variant: "destructive",
title: "Invalid target IP",
description: "Target IP must be within the site subnet",
});
return;
}
}
const newTarget: LocalTarget = { const newTarget: LocalTarget = {
...data, ...data,
enabled: true, enabled: true,
@ -602,3 +641,40 @@ export default function ReverseProxyTargets(props: {
</> </>
); );
} }
function isIPInSubnet(subnet: string, ip: string): boolean {
// Split subnet into IP and mask parts
const [subnetIP, maskBits] = subnet.split('/');
const mask = parseInt(maskBits);
if (mask < 0 || mask > 32) {
throw new Error('Invalid subnet mask. Must be between 0 and 32.');
}
// Convert IP addresses to binary numbers
const subnetNum = ipToNumber(subnetIP);
const ipNum = ipToNumber(ip);
// Calculate subnet mask
const maskNum = mask === 32 ? -1 : ~((1 << (32 - mask)) - 1);
// Check if the IP is in the subnet
return (subnetNum & maskNum) === (ipNum & maskNum);
}
function ipToNumber(ip: string): number {
// Validate IP address format
const parts = ip.split('.');
if (parts.length !== 4) {
throw new Error('Invalid IP address format');
}
// Convert IP octets to 32-bit number
return parts.reduce((num, octet) => {
const oct = parseInt(octet);
if (isNaN(oct) || oct < 0 || oct > 255) {
throw new Error('Invalid IP address octet');
}
return (num << 8) + oct;
}, 0);
}