Spaces:
Running
Running
File size: 4,948 Bytes
f326613 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { exchangeCodeForToken } from "../services/oauth";
import { secureStorage } from "../utils/storage";
import type { MCPServerConfig } from "../types/mcp";
import { STORAGE_KEYS, DEFAULTS } from "../config/constants";
interface OAuthTokens {
access_token: string;
refresh_token?: string;
expires_in?: number;
token_type?: string;
[key: string]: string | number | undefined;
}
interface OAuthCallbackProps {
serverUrl: string;
onSuccess?: (tokens: OAuthTokens) => void;
onError?: (error: Error) => void;
}
const OAuthCallback: React.FC<OAuthCallbackProps> = ({
serverUrl,
onSuccess,
onError,
}) => {
const [status, setStatus] = useState<string>("Authorizing...");
const navigate = useNavigate(); // Add this hook
useEffect(() => {
// Parse parameters from URL search params (OAuth providers send code in query string)
const parseHashParams = () => {
return new URLSearchParams(window.location.search);
};
const params = parseHashParams();
const code = params.get("code");
const state = params.get("state");
const error = params.get("error");
// Verify state parameter for CSRF protection
const savedState = localStorage.getItem('oauth_state');
if (state !== savedState) {
setStatus("Invalid state parameter. Possible CSRF attack.");
if (onError) onError(new Error("Invalid state parameter"));
return;
}
// Check for OAuth errors
if (error) {
const errorDescription = params.get("error_description") || error;
setStatus(`OAuth error: ${errorDescription}`);
if (onError) onError(new Error(errorDescription));
return;
}
// Always persist MCP server URL for robustness
localStorage.setItem(STORAGE_KEYS.OAUTH_MCP_SERVER_URL, serverUrl);
if (code) {
exchangeCodeForToken({
serverUrl,
code,
redirectUri: window.location.origin + "/#" + DEFAULTS.OAUTH_REDIRECT_PATH, // Add hash
})
.then(async (tokens) => {
await secureStorage.setItem(STORAGE_KEYS.OAUTH_ACCESS_TOKEN, tokens.access_token);
// Add MCP server to MCPClientService for UI
const mcpServerUrl = localStorage.getItem(STORAGE_KEYS.OAUTH_MCP_SERVER_URL);
if (mcpServerUrl) {
const serverName =
localStorage.getItem(STORAGE_KEYS.MCP_SERVER_NAME) || mcpServerUrl;
const serverTransport =
(localStorage.getItem(STORAGE_KEYS.MCP_SERVER_TRANSPORT) as MCPServerConfig['transport']) || DEFAULTS.MCP_TRANSPORT;
const serverConfig = {
id: `server_${Date.now()}`,
name: serverName,
url: mcpServerUrl,
enabled: true,
transport: serverTransport,
auth: {
type: "bearer" as const,
token: tokens.access_token,
},
};
let servers: MCPServerConfig[] = [];
try {
const stored = localStorage.getItem(STORAGE_KEYS.MCP_SERVERS);
if (stored) servers = JSON.parse(stored);
} catch {}
const exists = servers.some((s: MCPServerConfig) => s.url === mcpServerUrl);
if (!exists) {
servers.push(serverConfig);
localStorage.setItem(STORAGE_KEYS.MCP_SERVERS, JSON.stringify(servers));
}
// Clear temp values
localStorage.removeItem(STORAGE_KEYS.MCP_SERVER_NAME);
localStorage.removeItem(STORAGE_KEYS.MCP_SERVER_TRANSPORT);
localStorage.removeItem(STORAGE_KEYS.OAUTH_MCP_SERVER_URL);
}
// Clear OAuth state
localStorage.removeItem('oauth_state');
setStatus("Authorization successful! Redirecting...");
if (onSuccess) onSuccess(tokens);
// Use React Router navigation instead of window.location.replace
setTimeout(() => {
navigate("/", { replace: true });
}, 1000);
})
.catch((err) => {
setStatus("OAuth token exchange failed: " + err.message);
if (onError) onError(err);
// Clear OAuth state on error
localStorage.removeItem('oauth_state');
});
} else {
setStatus("Missing authorization code in callback URL.");
if (onError) onError(new Error("Missing authorization code"));
}
}, [serverUrl, onSuccess, onError, navigate]);
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p>{status}</p>
</div>
</div>
);
};
export default OAuthCallback; |