From 889f78ddb849fb70a433e5b056d17587f1f6ad03 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 4 Jun 2026 16:45:22 -0700 Subject: [PATCH] use resource name in ssh/rdp/vnc page meta --- .../browserGatewayTarget/getBrowserTarget.ts | 4 +- server/routers/browserGatewayTarget/types.ts | 1 + src/app/rdp/page.tsx | 31 ++----- src/app/ssh/page.tsx | 87 +++++++++---------- src/app/vnc/page.tsx | 30 ++----- src/lib/browserGatewayMetadata.ts | 13 +++ src/lib/getBrowserTargetForRequest.ts | 20 +++++ 7 files changed, 92 insertions(+), 94 deletions(-) create mode 100644 src/lib/browserGatewayMetadata.ts create mode 100644 src/lib/getBrowserTargetForRequest.ts diff --git a/server/private/routers/browserGatewayTarget/getBrowserTarget.ts b/server/private/routers/browserGatewayTarget/getBrowserTarget.ts index 7feda01e5..51e16de75 100644 --- a/server/private/routers/browserGatewayTarget/getBrowserTarget.ts +++ b/server/private/routers/browserGatewayTarget/getBrowserTarget.ts @@ -58,6 +58,7 @@ export async function getBrowserTarget( authToken: browserGatewayTarget.authToken, resourceId: resources.resourceId, niceId: resources.niceId, + name: resources.name, orgId: resources.orgId, pamMode: resources.pamMode, authDaemonMode: resources.authDaemonMode @@ -93,7 +94,8 @@ export async function getBrowserTarget( authDaemonMode: browserTarget.authDaemonMode, orgId: browserTarget.orgId, resourceId: browserTarget.resourceId, - niceId: browserTarget.niceId + niceId: browserTarget.niceId, + name: browserTarget.name }, success: true, error: false, diff --git a/server/routers/browserGatewayTarget/types.ts b/server/routers/browserGatewayTarget/types.ts index e644c952a..df6302391 100644 --- a/server/routers/browserGatewayTarget/types.ts +++ b/server/routers/browserGatewayTarget/types.ts @@ -5,6 +5,7 @@ export type GetBrowserTargetResponse = { orgId: string; resourceId: number; niceId: string; + name: string; pamMode: "passthrough" | "push" | null; authDaemonMode: "site" | "remote" | "native" | null; }; diff --git a/src/app/rdp/page.tsx b/src/app/rdp/page.tsx index 980edaf24..c6da3f4bf 100644 --- a/src/app/rdp/page.tsx +++ b/src/app/rdp/page.tsx @@ -1,34 +1,17 @@ -import { headers } from "next/headers"; -import { priv } from "@app/lib/api"; -import { AxiosResponse } from "axios"; -import { GetBrowserTargetResponse } from "@server/routers/browserGatewayTarget"; +import { generateBrowserGatewayMetadata } from "@app/lib/browserGatewayMetadata"; +import { getBrowserTargetForRequest } from "@app/lib/getBrowserTargetForRequest"; import RdpClient from "./RdpClient"; import AuthFooter from "@app/components/AuthFooter"; export const dynamic = "force-dynamic"; -export const metadata = { - title: "RDP" -}; +export async function generateMetadata() { + return generateBrowserGatewayMetadata("RDP"); +} export default async function RdpPage() { - const headersList = await headers(); - const host = headersList.get("host") || ""; - const hostname = host.split(":")[0]; - - let target: GetBrowserTargetResponse | null = null; - const error: string | null = null; - - try { - const res = await priv.get>( - `/resource/browser-target?fullDomain=${encodeURIComponent(hostname)}` - ); - target = res.data.data; - console.log("Fetched browser target:", target); - } catch (error) { - console.error("Error fetching browser target:", error); - error = "No resource found for this domain"; - } + const { target } = await getBrowserTargetForRequest(); + const error = target ? null : "No resource found for this domain"; return (
diff --git a/src/app/ssh/page.tsx b/src/app/ssh/page.tsx index 23cc9d908..44d5f1201 100644 --- a/src/app/ssh/page.tsx +++ b/src/app/ssh/page.tsx @@ -1,5 +1,7 @@ import { headers } from "next/headers"; import { priv } from "@app/lib/api"; +import { generateBrowserGatewayMetadata } from "@app/lib/browserGatewayMetadata"; +import { getBrowserTargetForRequest } from "@app/lib/getBrowserTargetForRequest"; import { AxiosResponse } from "axios"; import { GetBrowserTargetResponse } from "@server/routers/browserGatewayTarget"; import SshClient from "./SshClient"; @@ -99,14 +101,12 @@ function generateEphemeralKeyPair(): { export const dynamic = "force-dynamic"; -export const metadata = { - title: "SSH" -}; +export async function generateMetadata() { + return generateBrowserGatewayMetadata("SSH"); +} export default async function SshPage() { const headersList = await headers(); - const host = headersList.get("host") || ""; - const hostname = host.split(":")[0]; const cookieHeader = headersList.get("cookie") || ""; let target: GetBrowserTargetResponse | null = null; @@ -114,49 +114,44 @@ export default async function SshPage() { let privateKey: string | null = null; let error: string | null = null; - try { - const res = await priv.get>( - `/resource/browser-target?fullDomain=${encodeURIComponent(hostname)}` - ); - target = res.data.data; + const { target: browserTarget } = await getBrowserTargetForRequest(); + target = browserTarget; - if (target.pamMode === "push") { - try { - const { privateKeyPem, publicKeyOpenSSH } = - generateEphemeralKeyPair(); - privateKey = privateKeyPem; - const res = await priv.post>( - `/org/${target.orgId}/ssh/sign-key`, - { - publicKey: publicKeyOpenSSH, - resourceId: target.resourceId, - type: "public" - }, - { - headers: { - Cookie: cookieHeader - } - } - ); - signedKeyData = res.data.data; - - const messageIds = - signedKeyData.messageIds.length > 0 - ? signedKeyData.messageIds - : signedKeyData.messageId - ? [signedKeyData.messageId] - : []; - - await waitForRoundTripCompletion(messageIds, cookieHeader); - } catch (err) { - console.error("Error signing SSH key:", err); - error = - "Failed to sign SSH key for PAM push authentication. Did you sign in as a user?"; - } - } - } catch (err) { - console.error("Error fetching browser target:", err); + if (!target) { error = "No resource found for this domain"; + } else if (target.pamMode === "push") { + try { + const { privateKeyPem, publicKeyOpenSSH } = + generateEphemeralKeyPair(); + privateKey = privateKeyPem; + const res = await priv.post>( + `/org/${target.orgId}/ssh/sign-key`, + { + publicKey: publicKeyOpenSSH, + resourceId: target.resourceId, + type: "public" + }, + { + headers: { + Cookie: cookieHeader + } + } + ); + signedKeyData = res.data.data; + + const messageIds = + signedKeyData.messageIds.length > 0 + ? signedKeyData.messageIds + : signedKeyData.messageId + ? [signedKeyData.messageId] + : []; + + await waitForRoundTripCompletion(messageIds, cookieHeader); + } catch (err) { + console.error("Error signing SSH key:", err); + error = + "Failed to sign SSH key for PAM push authentication. Did you sign in as a user?"; + } } return ( diff --git a/src/app/vnc/page.tsx b/src/app/vnc/page.tsx index 7de845578..85eec047b 100644 --- a/src/app/vnc/page.tsx +++ b/src/app/vnc/page.tsx @@ -1,33 +1,17 @@ -import { headers } from "next/headers"; -import { priv } from "@app/lib/api"; -import { AxiosResponse } from "axios"; -import { GetBrowserTargetResponse } from "@server/routers/browserGatewayTarget"; +import { generateBrowserGatewayMetadata } from "@app/lib/browserGatewayMetadata"; +import { getBrowserTargetForRequest } from "@app/lib/getBrowserTargetForRequest"; import VncClient from "./VncClient"; import AuthFooter from "@app/components/AuthFooter"; export const dynamic = "force-dynamic"; -export const metadata = { - title: "VNC" -}; +export async function generateMetadata() { + return generateBrowserGatewayMetadata("VNC"); +} export default async function VncPage() { - const headersList = await headers(); - const host = headersList.get("host") || ""; - const hostname = host.split(":")[0]; - - let target: GetBrowserTargetResponse | null = null; - const error: string | null = null; - - try { - const res = await priv.get>( - `/resource/browser-target?fullDomain=${encodeURIComponent(hostname)}` - ); - target = res.data.data; - } catch (error) { - console.error("Error fetching browser target:", error); - error = "No resource found for this domain"; - } + const { target } = await getBrowserTargetForRequest(); + const error = target ? null : "No resource found for this domain"; return (
diff --git a/src/lib/browserGatewayMetadata.ts b/src/lib/browserGatewayMetadata.ts new file mode 100644 index 000000000..6d44c6b9e --- /dev/null +++ b/src/lib/browserGatewayMetadata.ts @@ -0,0 +1,13 @@ +import { getBrowserTargetForRequest } from "@app/lib/getBrowserTargetForRequest"; +import type { Metadata } from "next"; + +export async function generateBrowserGatewayMetadata( + protocol: "SSH" | "RDP" | "VNC" +): Promise { + const { target } = await getBrowserTargetForRequest(); + return { + title: target?.name + ? `${protocol} - ${target.name}` + : `${protocol} - Pangolin` + }; +} diff --git a/src/lib/getBrowserTargetForRequest.ts b/src/lib/getBrowserTargetForRequest.ts new file mode 100644 index 000000000..179e6e6f1 --- /dev/null +++ b/src/lib/getBrowserTargetForRequest.ts @@ -0,0 +1,20 @@ +import { priv } from "@app/lib/api"; +import { GetBrowserTargetResponse } from "@server/routers/browserGatewayTarget"; +import { AxiosResponse } from "axios"; +import { headers } from "next/headers"; +import { cache } from "react"; + +export const getBrowserTargetForRequest = cache(async () => { + const headersList = await headers(); + const host = headersList.get("host") || ""; + const hostname = host.split(":")[0]; + + try { + const res = await priv.get>( + `/resource/browser-target?fullDomain=${encodeURIComponent(hostname)}` + ); + return { target: res.data.data }; + } catch { + return { target: null }; + } +});