CoolFace
Apppublic

LiquidAI/LFM2-MCP

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