"use client"; import { useState, useEffect } from "react"; import { SettingsContainer, SettingsSection, SettingsSectionBody, SettingsSectionDescription, SettingsSectionFooter, SettingsSectionHeader, SettingsSectionTitle } from "@app/components/Settings"; import { Button } from "@app/components/ui/button"; import { createApiClient, formatAxiosError } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { toast } from "@app/hooks/useToast"; import { useParams, useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { PickSiteDefaultsResponse } from "@server/routers/site"; import { useSiteContext } from "@app/hooks/useSiteContext"; import { generateKeypair } from "../wireguardConfig"; import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog"; import { InfoSection, InfoSectionContent, InfoSections, InfoSectionTitle } from "@app/components/InfoSection"; import CopyToClipboard from "@app/components/CopyToClipboard"; import CopyTextBox from "@app/components/CopyTextBox"; import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert"; import { InfoIcon } from "lucide-react"; import { generateWireGuardConfig, generateObfuscatedWireGuardConfig } from "@app/lib/wireguard"; import { QRCodeCanvas } from "qrcode.react"; import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert"; import { NewtSiteInstallCommands } from "@app/components/newt-install-commands"; import { usePaidStatus } from "@app/hooks/usePaidStatus"; import { tierMatrix } from "@server/lib/billing/tierMatrix"; import type { AxiosResponse } from "axios"; import { useQuery } from "@tanstack/react-query"; import { productUpdatesQueries } from "@app/lib/queries"; export default function CredentialsPage() { const { env } = useEnvContext(); const api = createApiClient({ env }); const { orgId } = useParams(); const router = useRouter(); const t = useTranslations(); const { site } = useSiteContext(); const [modalOpen, setModalOpen] = useState(false); const [siteDefaults, setSiteDefaults] = useState(null); const [wgConfig, setWgConfig] = useState(""); const [publicKey, setPublicKey] = useState(""); const [currentNewtId, setCurrentNewtId] = useState( site.newtId ); const [regeneratedSecret, setRegeneratedSecret] = useState( null ); const [showCredentialsAlert, setShowCredentialsAlert] = useState(false); const [showWireGuardAlert, setShowWireGuardAlert] = useState(false); const [loadingDefaults, setLoadingDefaults] = useState(false); const [shouldDisconnect, setShouldDisconnect] = useState(true); const { isPaidUser } = usePaidStatus(); const { data: latestVersions } = useQuery( productUpdatesQueries.latestVersion(true) ); const newtVersion = latestVersions?.data?.newt?.latestVersion ?? "latest"; // Fetch site defaults for wireguard sites to show in obfuscated config useEffect(() => { const fetchSiteDefaults = async () => { if (site?.type === "wireguard" && !siteDefaults && orgId) { setLoadingDefaults(true); try { const res = await api.get( `/org/${orgId}/pick-site-defaults` ); if (res && res.status === 200) { setSiteDefaults(res.data.data); } } catch (error) { // Silently fail - we'll use site data or obfuscated values } finally { setLoadingDefaults(false); } } else { setLoadingDefaults(false); } }; fetchSiteDefaults(); }, []); const handleConfirmRegenerate = async () => { try { let generatedPublicKey = ""; let generatedWgConfig = ""; if (site?.type === "wireguard") { const generatedKeypair = generateKeypair(); generatedPublicKey = generatedKeypair.publicKey; setPublicKey(generatedPublicKey); const res = await api.get< AxiosResponse >(`/org/${orgId}/pick-site-defaults`); if (res && res.status === 200) { const data = res.data.data; setSiteDefaults(data); // generate config with the fetched data generatedWgConfig = generateWireGuardConfig( generatedKeypair.privateKey, generatedKeypair.publicKey, data.subnet, data.address, data.endpoint, data.listenPort ); setWgConfig(generatedWgConfig); setShowWireGuardAlert(true); } await api.post( `/re-key/${site?.siteId}/regenerate-site-secret`, { type: "wireguard", pubKey: generatedPublicKey } ); } if (site?.type === "newt") { const res = await api.get(`/org/${orgId}/pick-site-defaults`); if (res && res.status === 200) { const data = res.data.data; const rekeyRes = await api.post( `/re-key/${site?.siteId}/regenerate-site-secret`, { type: "newt", secret: data.newtSecret, disconnect: shouldDisconnect } ); if (rekeyRes && rekeyRes.status === 200) { const rekeyData = rekeyRes.data.data; if (rekeyData && rekeyData.newtId) { setCurrentNewtId(rekeyData.newtId); setRegeneratedSecret(data.newtSecret); setSiteDefaults({ ...data, newtId: rekeyData.newtId }); setShowCredentialsAlert(true); } } } } toast({ title: t("credentialsSaved"), description: t("credentialsSavedDescription") }); // ConfirmDeleteDialog handles closing the modal and triggering refresh via setOpen callback } catch (error) { toast({ variant: "destructive", title: t("error") || "Error", description: formatAxiosError(error) || t("credentialsRegenerateError") || "Failed to regenerate credentials" }); } }; const getConfirmationString = () => { return site?.name || site?.niceId || "My site"; }; const displayNewtId = currentNewtId ?? siteDefaults?.newtId ?? null; const displaySecret = regeneratedSecret ?? null; return ( <> {site?.type === "newt" && ( <> {t("siteNewtCredentials")} {t("siteNewtCredentialsDescription")} {t("newtEndpoint")} {t("newtId")} {displayNewtId ? ( ) : ( {"••••••••••••••••"} )} {t("newtSecretKey")} {displaySecret ? ( ) : ( { "••••••••••••••••••••••••••••••••" } )} {showCredentialsAlert && displaySecret && ( {t("siteCredentialsSave")} {t( "siteCredentialsSaveDescription" )} )} {!env.flags.disableEnterpriseFeatures && ( )} )} {site?.type === "wireguard" && ( {t("generatedcredentials")} {t("regenerateCredentials")} {!loadingDefaults && ( <> {wgConfig ? (
) : ( )} {showWireGuardAlert && wgConfig && ( {t("siteCredentialsSave")} {t( "siteCredentialsSaveDescription" )} )} )}
{!env.flags.disableEnterpriseFeatures && ( )}
)}
{site?.type === "newt" && ( { setModalOpen(val); // Prevent modal from reopening during refresh if (!val) { setTimeout(() => { router.refresh(); }, 150); } }} dialog={
{shouldDisconnect ? ( <>

{t( "siteRegenerateAndDisconnectConfirmation" )}

{t( "siteRegenerateAndDisconnectWarning" )}

) : ( <>

{t( "siteRegenerateCredentialsConfirmation" )}

{t("siteRegenerateCredentialsWarning")}

)}
} buttonText={ shouldDisconnect ? t("siteRegenerateAndDisconnect") : t("regenerateCredentialsButton") } onConfirm={handleConfirmRegenerate} string={getConfirmationString()} title={t("regenerateCredentials")} warningText={t("cannotbeUndone")} /> )} {site?.type === "wireguard" && ( { setModalOpen(val); // Prevent modal from reopening during refresh if (!val) { setTimeout(() => { router.refresh(); }, 150); } }} dialog={

{t("regenerateCredentialsConfirmation")}

{t("regenerateCredentialsWarning")}

} buttonText={t("regenerateCredentialsButton")} onConfirm={handleConfirmRegenerate} string={getConfirmationString()} title={t("regenerateCredentials")} warningText={t("cannotbeUndone")} /> )} ); }