Skip to content

Commit 726a4da

Browse files
authored
feat: allow magic string to generate session token for external apps (coder#9878)
* fix: make non-http external app links open in the current window * Allow magic string to be replaced with a session token for external apps * Improve readibility
1 parent 75366ec commit 726a4da

File tree

1 file changed

+37
-7
lines changed

1 file changed

+37
-7
lines changed

site/src/components/Resources/AppLink/AppLink.tsx

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ import { makeStyles } from "@mui/styles";
44
import Tooltip from "@mui/material/Tooltip";
55
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
66
import { PrimaryAgentButton } from "components/Resources/AgentButton";
7-
import { FC } from "react";
7+
import { FC, useState } from "react";
88
import { combineClasses } from "utils/combineClasses";
99
import * as TypesGen from "../../../api/typesGenerated";
1010
import { generateRandomString } from "../../../utils/random";
1111
import { BaseIcon } from "./BaseIcon";
1212
import { ShareIcon } from "./ShareIcon";
1313
import { useProxy } from "contexts/ProxyContext";
1414
import { createAppLinkHref } from "utils/apps";
15+
import { getApiKey } from "api/api";
1516

1617
const Language = {
1718
appTitle: (appName: string, identifier: string): string =>
@@ -28,6 +29,7 @@ export const AppLink: FC<AppLinkProps> = ({ app, workspace, agent }) => {
2829
const { proxy } = useProxy();
2930
const preferredPathBase = proxy.preferredPathAppURL;
3031
const appsHost = proxy.preferredWildcardHostname;
32+
const [fetchingSessionToken, setFetchingSessionToken] = useState(false);
3133

3234
const styles = useStyles();
3335
const username = workspace.owner_name;
@@ -72,6 +74,9 @@ export const AppLink: FC<AppLinkProps> = ({ app, workspace, agent }) => {
7274
primaryTooltip =
7375
"Your admin has not configured subdomain application access";
7476
}
77+
if (fetchingSessionToken) {
78+
canClick = false;
79+
}
7580

7681
const isPrivateApp = app.sharing_level === "owner";
7782

@@ -96,13 +101,38 @@ export const AppLink: FC<AppLinkProps> = ({ app, workspace, agent }) => {
96101
className={canClick ? styles.link : styles.disabledLink}
97102
onClick={
98103
canClick
99-
? (event) => {
104+
? async (event) => {
100105
event.preventDefault();
101-
window.open(
102-
href,
103-
Language.appTitle(appDisplayName, generateRandomString(12)),
104-
"width=900,height=600",
105-
);
106+
// This is an external URI like "vscode://", so
107+
// it needs to be opened with the browser protocol handler.
108+
if (app.external && !app.url.startsWith("http")) {
109+
// If the protocol is external the browser does not
110+
// redirect the user from the page.
111+
112+
// This is a magic undocumented string that is replaced
113+
// with a brand-new session token from the backend.
114+
// This only exists for external URLs, and should only
115+
// be used internally, and is highly subject to break.
116+
const magicTokenString = "$SESSION_TOKEN";
117+
const hasMagicToken = href.indexOf(magicTokenString);
118+
let url = href;
119+
if (hasMagicToken !== -1) {
120+
setFetchingSessionToken(true);
121+
const key = await getApiKey();
122+
url = href.replaceAll(magicTokenString, key.key);
123+
setFetchingSessionToken(false);
124+
}
125+
window.location.href = url;
126+
} else {
127+
window.open(
128+
href,
129+
Language.appTitle(
130+
appDisplayName,
131+
generateRandomString(12),
132+
),
133+
"width=900,height=600",
134+
);
135+
}
106136
}
107137
: undefined
108138
}

0 commit comments

Comments
 (0)