diff --git a/client/src/components/AppRenderer.tsx b/client/src/components/AppRenderer.tsx index e25f35c91..d8c44416b 100644 --- a/client/src/components/AppRenderer.tsx +++ b/client/src/components/AppRenderer.tsx @@ -31,6 +31,8 @@ interface AppRendererProps { onNotification?: (notification: ServerNotification) => void; } +const OAUTH_STATE_SESSION_KEY = "oauth_state"; + const AppRenderer = ({ sandboxPath, tool, @@ -74,8 +76,22 @@ const AppRenderer = ({ const handleOpenLink = async ({ url }: { url: string }) => { let isError = true; if (url.startsWith("https://") || url.startsWith("http://")) { - window.open(url, "_blank"); - isError = false; + try { + const nextUrl = new URL(url); + if (nextUrl.searchParams.get("response_type") === "code") { + const stateBytes = new Uint8Array(16); + window.crypto.getRandomValues(stateBytes); + const state = Array.from(stateBytes, (byte) => + byte.toString(16).padStart(2, "0"), + ).join(""); + sessionStorage.setItem(OAUTH_STATE_SESSION_KEY, state); + nextUrl.searchParams.set("state", state); + } + window.open(nextUrl.toString(), "_blank"); + isError = false; + } catch { + isError = true; + } } return { isError }; }; diff --git a/client/src/components/OAuthCallback.tsx b/client/src/components/OAuthCallback.tsx index ccfd6d928..282e31e0c 100644 --- a/client/src/components/OAuthCallback.tsx +++ b/client/src/components/OAuthCallback.tsx @@ -12,6 +12,8 @@ interface OAuthCallbackProps { onConnect: (serverUrl: string) => void; } +const OAUTH_STATE_SESSION_KEY = "oauth_state"; + const OAuthCallback = ({ onConnect }: OAuthCallbackProps) => { const { toast } = useToast(); const hasProcessedRef = useRef(false); @@ -36,6 +38,13 @@ const OAuthCallback = ({ onConnect }: OAuthCallbackProps) => { return notifyError(generateOAuthErrorDescription(params)); } + const callbackState = new URLSearchParams(window.location.search).get("state"); + const storedState = sessionStorage.getItem(OAUTH_STATE_SESSION_KEY); + if (!callbackState || !storedState || callbackState !== storedState) { + return notifyError("Invalid OAuth state"); + } + sessionStorage.removeItem(OAUTH_STATE_SESSION_KEY); + const serverUrl = sessionStorage.getItem(SESSION_KEYS.SERVER_URL); if (!serverUrl) { return notifyError("Missing Server URL");