mirror of
https://github.com/fosrl/pangolin.git
synced 2026-01-28 22:00:51 +00:00
show id in credential regen
This commit is contained in:
105
package-lock.json
generated
105
package-lock.json
generated
@@ -23502,6 +23502,111 @@
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-arm64": {
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.7.tgz",
|
||||
"integrity": "sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-x64": {
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.7.tgz",
|
||||
"integrity": "sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.7.tgz",
|
||||
"integrity": "sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-musl": {
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.7.tgz",
|
||||
"integrity": "sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-musl": {
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.7.tgz",
|
||||
"integrity": "sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.7.tgz",
|
||||
"integrity": "sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-x64-msvc": {
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.7.tgz",
|
||||
"integrity": "sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { db } from "@server/db";
|
||||
import { clients, clientSitesAssociationsCache } from "@server/db";
|
||||
import { db, olms } from "@server/db";
|
||||
import { clients } from "@server/db";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
@@ -13,7 +13,7 @@ import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const getClientSchema = z.strictObject({
|
||||
clientId: z.string().transform(stoi).pipe(z.int().positive())
|
||||
});
|
||||
});
|
||||
|
||||
async function query(clientId: number) {
|
||||
// Get the client
|
||||
@@ -21,26 +21,20 @@ async function query(clientId: number) {
|
||||
.select()
|
||||
.from(clients)
|
||||
.where(and(eq(clients.clientId, clientId)))
|
||||
.leftJoin(olms, eq(clients.olmId, olms.olmId))
|
||||
.limit(1);
|
||||
|
||||
if (!client) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the siteIds associated with this client
|
||||
const sites = await db
|
||||
.select({ siteId: clientSitesAssociationsCache.siteId })
|
||||
.from(clientSitesAssociationsCache)
|
||||
.where(eq(clientSitesAssociationsCache.clientId, clientId));
|
||||
|
||||
// Add the siteIds to the client object
|
||||
return {
|
||||
...client,
|
||||
siteIds: sites.map((site) => site.siteId)
|
||||
};
|
||||
return client;
|
||||
}
|
||||
|
||||
export type GetClientResponse = NonNullable<Awaited<ReturnType<typeof query>>>;
|
||||
export type GetClientResponse = NonNullable<
|
||||
Awaited<ReturnType<typeof query>>
|
||||
>["clients"] & {
|
||||
olmId: string | null;
|
||||
};
|
||||
|
||||
registry.registerPath({
|
||||
method: "get",
|
||||
@@ -82,8 +76,13 @@ export async function getClient(
|
||||
);
|
||||
}
|
||||
|
||||
const data: GetClientResponse = {
|
||||
...client.clients,
|
||||
olmId: client.olms ? client.olms.olmId : null
|
||||
};
|
||||
|
||||
return response<GetClientResponse>(res, {
|
||||
data: client,
|
||||
data,
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Client retrieved successfully",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { db } from "@server/db";
|
||||
import { db, newts } from "@server/db";
|
||||
import { sites } from "@server/db";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import response from "@server/lib/response";
|
||||
@@ -20,7 +20,7 @@ const getSiteSchema = z.strictObject({
|
||||
.optional(),
|
||||
niceId: z.string().optional(),
|
||||
orgId: z.string().optional()
|
||||
});
|
||||
});
|
||||
|
||||
async function query(siteId?: number, niceId?: string, orgId?: string) {
|
||||
if (siteId) {
|
||||
@@ -28,6 +28,7 @@ async function query(siteId?: number, niceId?: string, orgId?: string) {
|
||||
.select()
|
||||
.from(sites)
|
||||
.where(eq(sites.siteId, siteId))
|
||||
.leftJoin(newts, eq(sites.siteId, newts.siteId))
|
||||
.limit(1);
|
||||
return res;
|
||||
} else if (niceId && orgId) {
|
||||
@@ -35,12 +36,15 @@ async function query(siteId?: number, niceId?: string, orgId?: string) {
|
||||
.select()
|
||||
.from(sites)
|
||||
.where(and(eq(sites.niceId, niceId), eq(sites.orgId, orgId)))
|
||||
.leftJoin(newts, eq(sites.siteId, newts.siteId))
|
||||
.limit(1);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
export type GetSiteResponse = NonNullable<Awaited<ReturnType<typeof query>>>;
|
||||
export type GetSiteResponse = NonNullable<
|
||||
Awaited<ReturnType<typeof query>>
|
||||
>["sites"] & { newtId: string | null };
|
||||
|
||||
registry.registerPath({
|
||||
method: "get",
|
||||
@@ -94,8 +98,13 @@ export async function getSite(
|
||||
return next(createHttpError(HttpCode.NOT_FOUND, "Site not found"));
|
||||
}
|
||||
|
||||
const data: GetSiteResponse = {
|
||||
...site.sites,
|
||||
newtId: site.newt ? site.newt.newtId : null
|
||||
};
|
||||
|
||||
return response<GetSiteResponse>(res, {
|
||||
data: site,
|
||||
data,
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Site retrieved successfully",
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
SettingsSection,
|
||||
SettingsSectionBody,
|
||||
SettingsSectionDescription,
|
||||
SettingsSectionFooter,
|
||||
SettingsSectionHeader,
|
||||
SettingsSectionTitle
|
||||
} from "@app/components/Settings";
|
||||
@@ -21,11 +22,20 @@ import {
|
||||
QuickStartRemoteExitNodeResponse
|
||||
} from "@server/routers/remoteExitNode/types";
|
||||
import { useRemoteExitNodeContext } from "@app/hooks/useRemoteExitNodeContext";
|
||||
import RegenerateCredentialsModal from "@app/components/RegenerateCredentialsModal";
|
||||
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
|
||||
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
|
||||
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
|
||||
import { build } from "@server/build";
|
||||
import { SecurityFeaturesAlert } from "@app/components/SecurityFeaturesAlert";
|
||||
import {
|
||||
InfoSection,
|
||||
InfoSectionContent,
|
||||
InfoSections,
|
||||
InfoSectionTitle
|
||||
} from "@app/components/InfoSection";
|
||||
import CopyToClipboard from "@app/components/CopyToClipboard";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
|
||||
import { InfoIcon } from "lucide-react";
|
||||
|
||||
export default function CredentialsPage() {
|
||||
const { env } = useEnvContext();
|
||||
@@ -38,6 +48,13 @@ export default function CredentialsPage() {
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [credentials, setCredentials] =
|
||||
useState<PickRemoteExitNodeDefaultsResponse | null>(null);
|
||||
const [currentRemoteExitNodeId, setCurrentRemoteExitNodeId] = useState<
|
||||
string | null
|
||||
>(remoteExitNode.remoteExitNodeId);
|
||||
const [regeneratedSecret, setRegeneratedSecret] = useState<string | null>(
|
||||
null
|
||||
);
|
||||
const [showCredentialsAlert, setShowCredentialsAlert] = useState(false);
|
||||
|
||||
const { licenseStatus, isUnlocked } = useLicenseStatusContext();
|
||||
const subscription = useSubscriptionStatusContext();
|
||||
@@ -50,6 +67,7 @@ export default function CredentialsPage() {
|
||||
};
|
||||
|
||||
const handleConfirmRegenerate = async () => {
|
||||
try {
|
||||
const response = await api.get<
|
||||
AxiosResponse<PickRemoteExitNodeDefaultsResponse>
|
||||
>(`/org/${orgId}/pick-remote-exit-node-defaults`);
|
||||
@@ -57,32 +75,54 @@ export default function CredentialsPage() {
|
||||
const data = response.data.data;
|
||||
setCredentials(data);
|
||||
|
||||
await api.put<AxiosResponse<QuickStartRemoteExitNodeResponse>>(
|
||||
`/re-key/${orgId}/regenerate-remote-exit-node-secret`,
|
||||
{
|
||||
const rekeyRes = await api.put<
|
||||
AxiosResponse<QuickStartRemoteExitNodeResponse>
|
||||
>(`/re-key/${orgId}/regenerate-remote-exit-node-secret`, {
|
||||
remoteExitNodeId: remoteExitNode.remoteExitNodeId,
|
||||
secret: data.secret
|
||||
});
|
||||
|
||||
if (rekeyRes && rekeyRes.status === 200) {
|
||||
const rekeyData = rekeyRes.data.data;
|
||||
if (rekeyData && rekeyData.remoteExitNodeId) {
|
||||
setCurrentRemoteExitNodeId(rekeyData.remoteExitNodeId);
|
||||
setRegeneratedSecret(data.secret);
|
||||
setCredentials({
|
||||
...data,
|
||||
remoteExitNodeId: rekeyData.remoteExitNodeId
|
||||
});
|
||||
setShowCredentialsAlert(true);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
toast({
|
||||
title: t("credentialsSaved"),
|
||||
description: t("credentialsSavedDescription")
|
||||
});
|
||||
|
||||
router.refresh();
|
||||
};
|
||||
|
||||
const getCredentials = () => {
|
||||
if (credentials) {
|
||||
return {
|
||||
Id: remoteExitNode.remoteExitNodeId,
|
||||
Secret: credentials.secret
|
||||
};
|
||||
} catch (error) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: t("error") || "Error",
|
||||
description:
|
||||
formatAxiosError(error) ||
|
||||
t("credentialsRegenerateError") ||
|
||||
"Failed to regenerate credentials"
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const getConfirmationString = () => {
|
||||
return (
|
||||
remoteExitNode?.name ||
|
||||
remoteExitNode?.remoteExitNodeId ||
|
||||
"My remote exit node"
|
||||
);
|
||||
};
|
||||
|
||||
const displayRemoteExitNodeId =
|
||||
currentRemoteExitNodeId || remoteExitNode?.remoteExitNodeId || null;
|
||||
const displaySecret = regeneratedSecret || null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsContainer>
|
||||
@@ -95,26 +135,96 @@ export default function CredentialsPage() {
|
||||
{t("regenerateCredentials")}
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
|
||||
<SettingsSectionBody>
|
||||
<SecurityFeaturesAlert />
|
||||
<InfoSections cols={3}>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t("endpoint") || "Endpoint"}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
<CopyToClipboard
|
||||
text={env.app.dashboardUrl}
|
||||
/>
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t("remoteExitNodeId") ||
|
||||
"Remote Exit Node ID"}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
{displayRemoteExitNodeId ? (
|
||||
<CopyToClipboard
|
||||
text={displayRemoteExitNodeId}
|
||||
/>
|
||||
) : (
|
||||
<span>{"••••••••••••••••"}</span>
|
||||
)}
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t("secretKey") || "Secret Key"}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
{displaySecret ? (
|
||||
<CopyToClipboard text={displaySecret} />
|
||||
) : (
|
||||
<span>
|
||||
{"••••••••••••••••••••••••••••••••"}
|
||||
</span>
|
||||
)}
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
</InfoSections>
|
||||
|
||||
{showCredentialsAlert && displaySecret && (
|
||||
<Alert variant="neutral" className="mt-4">
|
||||
<InfoIcon className="h-4 w-4" />
|
||||
<AlertTitle className="font-semibold">
|
||||
{t("credentialsSave") ||
|
||||
"Save the Credentials"}
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
{t("credentialsSaveDescription") ||
|
||||
"You will only be able to see this once. Make sure to copy it to a secure place."}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
</SettingsSectionBody>
|
||||
<SettingsSectionFooter>
|
||||
<Button
|
||||
onClick={() => setModalOpen(true)}
|
||||
disabled={isSecurityFeatureDisabled()}
|
||||
>
|
||||
{t("regeneratecredentials")}
|
||||
{t("regenerateCredentialsButton")}
|
||||
</Button>
|
||||
</SettingsSectionBody>
|
||||
</SettingsSectionFooter>
|
||||
</SettingsSection>
|
||||
</SettingsContainer>
|
||||
|
||||
<RegenerateCredentialsModal
|
||||
<ConfirmDeleteDialog
|
||||
open={modalOpen}
|
||||
onOpenChange={setModalOpen}
|
||||
type="remote-exit-node"
|
||||
onConfirmRegenerate={handleConfirmRegenerate}
|
||||
dashboardUrl={env.app.dashboardUrl}
|
||||
credentials={getCredentials()}
|
||||
setOpen={(val) => {
|
||||
setModalOpen(val);
|
||||
// Prevent modal from reopening during refresh
|
||||
if (!val) {
|
||||
setTimeout(() => {
|
||||
router.refresh();
|
||||
}, 150);
|
||||
}
|
||||
}}
|
||||
dialog={
|
||||
<div className="space-y-2">
|
||||
<p>{t("regenerateCredentialsConfirmation")}</p>
|
||||
<p>{t("regenerateCredentialsWarning")}</p>
|
||||
</div>
|
||||
}
|
||||
buttonText={t("regenerateCredentialsButton")}
|
||||
onConfirm={handleConfirmRegenerate}
|
||||
string={getConfirmationString()}
|
||||
title={t("regenerateCredentials")}
|
||||
warningText={t("cannotbeUndone")}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -44,7 +44,7 @@ export default function CredentialsPage() {
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [clientDefaults, setClientDefaults] =
|
||||
useState<PickClientDefaultsResponse | null>(null);
|
||||
const [currentOlmId, setCurrentOlmId] = useState<string | null>(null);
|
||||
const [currentOlmId, setCurrentOlmId] = useState<string | null>(client.olmId);
|
||||
const [regeneratedSecret, setRegeneratedSecret] = useState<string | null>(
|
||||
null
|
||||
);
|
||||
@@ -154,7 +154,7 @@ export default function CredentialsPage() {
|
||||
{displaySecret ? (
|
||||
<CopyToClipboard text={displaySecret} />
|
||||
) : (
|
||||
<span>{"••••••••••••••••"}</span>
|
||||
<span>{"••••••••••••••••••••••••••••••••"}</span>
|
||||
)}
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
|
||||
@@ -53,7 +53,7 @@ export default function CredentialsPage() {
|
||||
useState<PickSiteDefaultsResponse | null>(null);
|
||||
const [wgConfig, setWgConfig] = useState("");
|
||||
const [publicKey, setPublicKey] = useState("");
|
||||
const [currentNewtId, setCurrentNewtId] = useState<string | null>(null);
|
||||
const [currentNewtId, setCurrentNewtId] = useState<string | null>(site.newtId);
|
||||
const [regeneratedSecret, setRegeneratedSecret] = useState<string | null>(
|
||||
null
|
||||
);
|
||||
@@ -233,7 +233,7 @@ export default function CredentialsPage() {
|
||||
text={displaySecret}
|
||||
/>
|
||||
) : (
|
||||
<span>{"••••••••••••••••"}</span>
|
||||
<span>{"••••••••••••••••••••••••••••••••"}</span>
|
||||
)}
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
|
||||
Reference in New Issue
Block a user