From f8c61ae35b15edd0994edab8daae3caf764605f7 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Tue, 26 Sep 2023 18:25:22 +0000 Subject: [PATCH 1/3] fix: make non-http external app links open in the current window --- .../components/Resources/AppLink/AppLink.tsx | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/site/src/components/Resources/AppLink/AppLink.tsx b/site/src/components/Resources/AppLink/AppLink.tsx index 9075fec95928a..85c16272789c4 100644 --- a/site/src/components/Resources/AppLink/AppLink.tsx +++ b/site/src/components/Resources/AppLink/AppLink.tsx @@ -98,11 +98,22 @@ export const AppLink: FC = ({ app, workspace, agent }) => { canClick ? (event) => { event.preventDefault(); - window.open( - href, - Language.appTitle(appDisplayName, generateRandomString(12)), - "width=900,height=600", - ); + // This is an external URI like "vscode://", so + // it needs to be opened with the browser protocol handler. + if (app.external && !app.url.startsWith("http")) { + // If the protocol is external the browser does not + // redirect the user from the page. + window.location.href = href; + } else { + window.open( + href, + Language.appTitle( + appDisplayName, + generateRandomString(12), + ), + "width=900,height=600", + ); + } } : undefined } From d0710a703da581f20bb5f768a508e25d3b6df573 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Tue, 26 Sep 2023 18:44:36 +0000 Subject: [PATCH 2/3] Allow magic string to be replaced with a session token for external apps --- .../components/Resources/AppLink/AppLink.tsx | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/site/src/components/Resources/AppLink/AppLink.tsx b/site/src/components/Resources/AppLink/AppLink.tsx index 85c16272789c4..d4bf1985dda9a 100644 --- a/site/src/components/Resources/AppLink/AppLink.tsx +++ b/site/src/components/Resources/AppLink/AppLink.tsx @@ -4,7 +4,7 @@ import { makeStyles } from "@mui/styles"; import Tooltip from "@mui/material/Tooltip"; import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline"; import { PrimaryAgentButton } from "components/Resources/AgentButton"; -import { FC } from "react"; +import { FC, useState } from "react"; import { combineClasses } from "utils/combineClasses"; import * as TypesGen from "../../../api/typesGenerated"; import { generateRandomString } from "../../../utils/random"; @@ -12,6 +12,7 @@ import { BaseIcon } from "./BaseIcon"; import { ShareIcon } from "./ShareIcon"; import { useProxy } from "contexts/ProxyContext"; import { createAppLinkHref } from "utils/apps"; +import { getApiKey } from "api/api"; const Language = { appTitle: (appName: string, identifier: string): string => @@ -28,6 +29,7 @@ export const AppLink: FC = ({ app, workspace, agent }) => { const { proxy } = useProxy(); const preferredPathBase = proxy.preferredPathAppURL; const appsHost = proxy.preferredWildcardHostname; + const [fetchingSessionToken, setFetchingSessionToken] = useState(false); const styles = useStyles(); const username = workspace.owner_name; @@ -72,6 +74,9 @@ export const AppLink: FC = ({ app, workspace, agent }) => { primaryTooltip = "Your admin has not configured subdomain application access"; } + if (fetchingSessionToken) { + canClick = false; + } const isPrivateApp = app.sharing_level === "owner"; @@ -103,7 +108,31 @@ export const AppLink: FC = ({ app, workspace, agent }) => { if (app.external && !app.url.startsWith("http")) { // If the protocol is external the browser does not // redirect the user from the page. - window.location.href = href; + + // This is a magic undocumented string that is replaced + // with a brand-new session token from the backend. + // This only exists for external URLs, and should only + // be used internally, and is highly subject to break. + const magicTokenString = "$SESSION_TOKEN"; + const hasMagicToken = href.indexOf(magicTokenString); + if (hasMagicToken !== -1) { + setFetchingSessionToken(true); + getApiKey() + .then((key) => { + const url = href.replaceAll( + magicTokenString, + key.key, + ); + window.location.href = url; + setFetchingSessionToken(false); + }) + .catch((ex) => { + console.error(ex); + setFetchingSessionToken(false); + }); + } else { + window.location.href = href; + } } else { window.open( href, From d0ddd4e9f573ad8d736729d1c907cf2007edea81 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Tue, 26 Sep 2023 20:48:20 +0000 Subject: [PATCH 3/3] Improve readibility --- .../components/Resources/AppLink/AppLink.tsx | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/site/src/components/Resources/AppLink/AppLink.tsx b/site/src/components/Resources/AppLink/AppLink.tsx index d4bf1985dda9a..5e49d43816334 100644 --- a/site/src/components/Resources/AppLink/AppLink.tsx +++ b/site/src/components/Resources/AppLink/AppLink.tsx @@ -101,7 +101,7 @@ export const AppLink: FC = ({ app, workspace, agent }) => { className={canClick ? styles.link : styles.disabledLink} onClick={ canClick - ? (event) => { + ? async (event) => { event.preventDefault(); // This is an external URI like "vscode://", so // it needs to be opened with the browser protocol handler. @@ -115,24 +115,14 @@ export const AppLink: FC = ({ app, workspace, agent }) => { // be used internally, and is highly subject to break. const magicTokenString = "$SESSION_TOKEN"; const hasMagicToken = href.indexOf(magicTokenString); + let url = href; if (hasMagicToken !== -1) { setFetchingSessionToken(true); - getApiKey() - .then((key) => { - const url = href.replaceAll( - magicTokenString, - key.key, - ); - window.location.href = url; - setFetchingSessionToken(false); - }) - .catch((ex) => { - console.error(ex); - setFetchingSessionToken(false); - }); - } else { - window.location.href = href; + const key = await getApiKey(); + url = href.replaceAll(magicTokenString, key.key); + setFetchingSessionToken(false); } + window.location.href = url; } else { window.open( href,