diff --git a/server/auth/actions.ts b/server/auth/actions.ts index 6adbf0d6..8bc7b823 100644 --- a/server/auth/actions.ts +++ b/server/auth/actions.ts @@ -10,6 +10,7 @@ export enum ActionsEnum { // deleteOrg = "deleteOrg", getOrg = "getOrg", updateOrg = "updateOrg", + deleteOrg = "deleteOrg", createSite = "createSite", deleteSite = "deleteSite", getSite = "getSite", diff --git a/server/middlewares/verifySiteAccess.ts b/server/middlewares/verifySiteAccess.ts index dc1a87d5..dcf35f68 100644 --- a/server/middlewares/verifySiteAccess.ts +++ b/server/middlewares/verifySiteAccess.ts @@ -29,7 +29,6 @@ export async function verifySiteAccess( } if (isNaN(siteId)) { - logger.debug(JSON.stringify(req.body)); return next(createHttpError(HttpCode.BAD_REQUEST, "Invalid site ID")); } diff --git a/server/routers/auth/login.ts b/server/routers/auth/login.ts index b7b68e47..39d01825 100644 --- a/server/routers/auth/login.ts +++ b/server/routers/auth/login.ts @@ -126,8 +126,6 @@ export async function login( await createSession(token, existingUser.userId); const cookie = serializeSessionCookie(token); - logger.debug(cookie); - res.appendHeader("Set-Cookie", cookie); if ( diff --git a/server/routers/auth/requestPasswordReset.ts b/server/routers/auth/requestPasswordReset.ts index e2cdb48f..b9f6c6c5 100644 --- a/server/routers/auth/requestPasswordReset.ts +++ b/server/routers/auth/requestPasswordReset.ts @@ -75,8 +75,6 @@ export async function requestPasswordReset( // TODO: send email with link to reset password on dashboard // something like: https://example.com/auth/reset-password?email=${email}&?token=${token} // for now, just log the token - logger.debug(`Password reset token: ${token}`); - return response(res, { data: { sentEmail: true, diff --git a/server/routers/org/deleteOrg.ts b/server/routers/org/deleteOrg.ts index 5bbecb9a..125b49a5 100644 --- a/server/routers/org/deleteOrg.ts +++ b/server/routers/org/deleteOrg.ts @@ -1,7 +1,13 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import { db } from "@server/db"; -import { orgs, userActions } from "@server/db/schema"; +import { + newts, + newtSessions, + orgs, + sites, + userActions +} from "@server/db/schema"; import { eq } from "drizzle-orm"; import response from "@server/utils/response"; import HttpCode from "@server/types/HttpCode"; @@ -9,9 +15,11 @@ import createHttpError from "http-errors"; import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions"; import logger from "@server/logger"; import { fromError } from "zod-validation-error"; +import { sendToClient } from "../ws"; +import { deletePeer } from "../gerbil/peers"; const deleteOrgSchema = z.object({ - orgId: z.string(), + orgId: z.string() }); export async function deleteOrg( @@ -32,26 +40,27 @@ export async function deleteOrg( const { orgId } = parsedParams.data; - // // Check if the user has permission to list sites - // const hasPermission = await checkUserActionPermission( - // ActionsEnum.deleteOrg, - // req - // ); - // if (!hasPermission) { - // return next( - // createHttpError( - // HttpCode.FORBIDDEN, - // "User does not have permission to perform this action" - // ) - // ); - // } + // Check if the user has permission to list sites + const hasPermission = await checkUserActionPermission( + ActionsEnum.deleteOrg, + req + ); + if (!hasPermission) { + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have permission to perform this action" + ) + ); + } - const deletedOrg = await db - .delete(orgs) + const [org] = await db + .select() + .from(orgs) .where(eq(orgs.orgId, orgId)) - .returning(); + .limit(1); - if (deletedOrg.length === 0) { + if (!org) { return next( createHttpError( HttpCode.NOT_FOUND, @@ -60,12 +69,53 @@ export async function deleteOrg( ); } + // we need to handle deleting each site + const orgSites = await db + .select() + .from(sites) + .where(eq(sites.orgId, orgId)) + .limit(1); + + if (sites) { + for (const site of orgSites) { + if (site.pubKey) { + if (site.type == "wireguard") { + await deletePeer(site.exitNodeId!, site.pubKey); + } else if (site.type == "newt") { + // get the newt on the site by querying the newt table for siteId + const [deletedNewt] = await db + .delete(newts) + .where(eq(newts.siteId, site.siteId)) + .returning(); + if (deletedNewt) { + const payload = { + type: `newt/terminate`, + data: {} + }; + sendToClient(deletedNewt.newtId, payload); + + // delete all of the sessions for the newt + db.delete(newtSessions) + .where( + eq(newtSessions.newtId, deletedNewt.newtId) + ) + .run(); + } + } + } + + db.delete(sites).where(eq(sites.siteId, site.siteId)).run(); + } + } + + await db.delete(orgs).where(eq(orgs.orgId, orgId)).returning(); + return response(res, { data: null, success: true, error: false, message: "Organization deleted successfully", - status: HttpCode.OK, + status: HttpCode.OK }); } catch (error) { logger.error(error); diff --git a/server/routers/org/updateOrg.ts b/server/routers/org/updateOrg.ts index 4e700617..455465fe 100644 --- a/server/routers/org/updateOrg.ts +++ b/server/routers/org/updateOrg.ts @@ -16,7 +16,7 @@ const updateOrgParamsSchema = z.object({ const updateOrgBodySchema = z .object({ name: z.string().min(1).max(255).optional(), - domain: z.string().min(1).max(255).optional(), + // domain: z.string().min(1).max(255).optional(), }) .strict() .refine((data) => Object.keys(data).length > 0, { diff --git a/server/routers/resource/authWithAccessToken.ts b/server/routers/resource/authWithAccessToken.ts index 5e43a04d..cb985021 100644 --- a/server/routers/resource/authWithAccessToken.ts +++ b/server/routers/resource/authWithAccessToken.ts @@ -104,7 +104,6 @@ export async function authWithAccessToken( // outputLen: 32, // parallelism: 1 // }); - logger.debug(`${accessToken} ${tokenItem.tokenHash}`) const validCode = accessToken === tokenItem.tokenHash; if (!validCode) { diff --git a/src/app/[orgId]/settings/general/page.tsx b/src/app/[orgId]/settings/general/page.tsx index d97e746e..0e9635fa 100644 --- a/src/app/[orgId]/settings/general/page.tsx +++ b/src/app/[orgId]/settings/general/page.tsx @@ -4,16 +4,85 @@ import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog"; import { Button } from "@app/components/ui/button"; import { useOrgContext } from "@app/hooks/useOrgContext"; import { userOrgUserContext } from "@app/hooks/useOrgUserContext"; +import { useToast } from "@app/hooks/useToast"; import { useState } from "react"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { z } from "zod"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { createApiClient } from "@app/api"; +import { useEnvContext } from "@app/hooks/useEnvContext"; +import { formatAxiosError } from "@app/lib/utils"; +import { AlertTriangle, Trash2 } from "lucide-react"; +import { + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle +} from "@/components/ui/card"; + +const GeneralFormSchema = z.object({ + name: z.string() +}); + +type GeneralFormValues = z.infer; export default function GeneralPage() { const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const { orgUser } = userOrgUserContext(); const { org } = useOrgContext(); + const { toast } = useToast(); + const api = createApiClient(useEnvContext()); + + const form = useForm({ + resolver: zodResolver(GeneralFormSchema), + defaultValues: { + name: org?.org.name + }, + mode: "onChange" + }); async function deleteOrg() { - console.log("not implemented"); + await api + .delete(`/org/${org?.org.orgId}`) + .catch((e) => { + toast({ + variant: "destructive", + title: "Failed to delete org", + description: formatAxiosError( + e, + "An error occurred while deleting the org." + ), + }); + }); + } + + async function onSubmit(data: GeneralFormValues) { + await api + .post(`/org/${org?.org.orgId}`, { + name: data.name + }) + .catch((e) => { + toast({ + variant: "destructive", + title: "Failed to update org", + description: formatAxiosError( + e, + "An error occurred while updating the org." + ) + }); + }); } return ( @@ -46,15 +115,55 @@ export default function GeneralPage() { title="Delete organization" /> -
- {orgUser.isOwner ? ( - + + + + + + + + Danger Zone + + + +

+ Once you delete this org, there is no going back. Please + be certain. +

+
+ + - ) : ( -

Nothing to see here

- )} -
+ + ); }