mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-11 22:51:28 +00:00
Add local storage auth (#13473)
This commit is contained in:
parent
67833590d6
commit
95fbe59c46
4 changed files with 210 additions and 21 deletions
|
|
@ -0,0 +1,111 @@
|
|||
// Utility functions for managing MCP server authentication tokens in localStorage
|
||||
|
||||
const MCP_AUTH_STORAGE_KEY = 'litellm_mcp_auth_tokens';
|
||||
|
||||
export interface MCPAuthToken {
|
||||
serverId: string;
|
||||
serverAlias?: string;
|
||||
authValue: string;
|
||||
authType: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export interface MCPAuthStorage {
|
||||
[serverId: string]: MCPAuthToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all stored MCP authentication tokens
|
||||
*/
|
||||
export const getMCPAuthTokens = (): MCPAuthStorage => {
|
||||
try {
|
||||
const stored = localStorage.getItem(MCP_AUTH_STORAGE_KEY);
|
||||
return stored ? JSON.parse(stored) : {};
|
||||
} catch (error) {
|
||||
console.error('Error reading MCP auth tokens from localStorage:', error);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get authentication token for a specific MCP server
|
||||
*/
|
||||
export const getMCPAuthToken = (serverId: string, serverAlias?: string): string | null => {
|
||||
try {
|
||||
const tokens = getMCPAuthTokens();
|
||||
const token = tokens[serverId];
|
||||
|
||||
// If token exists, check if serverAlias matches (both can be undefined)
|
||||
if (token && token.serverAlias === serverAlias) {
|
||||
return token.authValue;
|
||||
}
|
||||
|
||||
// If no serverAlias was provided and token exists without serverAlias, return it
|
||||
if (token && !serverAlias && !token.serverAlias) {
|
||||
return token.authValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Error getting MCP auth token:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Store authentication token for an MCP server
|
||||
*/
|
||||
export const setMCPAuthToken = (
|
||||
serverId: string,
|
||||
authValue: string,
|
||||
authType: string,
|
||||
serverAlias?: string
|
||||
): void => {
|
||||
try {
|
||||
const tokens = getMCPAuthTokens();
|
||||
|
||||
tokens[serverId] = {
|
||||
serverId,
|
||||
serverAlias,
|
||||
authValue,
|
||||
authType,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
localStorage.setItem(MCP_AUTH_STORAGE_KEY, JSON.stringify(tokens));
|
||||
} catch (error) {
|
||||
console.error('Error storing MCP auth token:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove authentication token for an MCP server
|
||||
*/
|
||||
export const removeMCPAuthToken = (serverId: string): void => {
|
||||
try {
|
||||
const tokens = getMCPAuthTokens();
|
||||
delete tokens[serverId];
|
||||
localStorage.setItem(MCP_AUTH_STORAGE_KEY, JSON.stringify(tokens));
|
||||
} catch (error) {
|
||||
console.error('Error removing MCP auth token:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear all MCP authentication tokens (useful for logout)
|
||||
*/
|
||||
export const clearMCPAuthTokens = (): void => {
|
||||
try {
|
||||
localStorage.removeItem(MCP_AUTH_STORAGE_KEY);
|
||||
} catch (error) {
|
||||
console.error('Error clearing MCP auth tokens:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a token exists for a server
|
||||
*/
|
||||
export const hasMCPAuthToken = (serverId: string, serverAlias?: string): boolean => {
|
||||
const token = getMCPAuthToken(serverId, serverAlias);
|
||||
return token !== null;
|
||||
};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||
import { ToolTestPanel } from "./ToolTestPanel";
|
||||
import {
|
||||
|
|
@ -8,8 +8,14 @@ import {
|
|||
mcpServerHasAuth,
|
||||
} from "./types";
|
||||
import { listMCPTools, callMCPTool } from "../networking";
|
||||
import {
|
||||
getMCPAuthToken,
|
||||
setMCPAuthToken,
|
||||
removeMCPAuthToken,
|
||||
hasMCPAuthToken
|
||||
} from "./mcp_auth_storage";
|
||||
|
||||
import { Modal, Input, Form } from "antd";
|
||||
import { Modal, Input, Form, message } from "antd";
|
||||
import { Button, Card, Title, Text } from "@tremor/react";
|
||||
import { RobotOutlined, ApiOutlined, KeyOutlined, SafetyOutlined, ToolOutlined } from "@ant-design/icons";
|
||||
|
||||
|
|
@ -92,10 +98,12 @@ export const AuthModal = ({
|
|||
const AuthSection = ({
|
||||
authType,
|
||||
onAuthSubmit,
|
||||
onClearAuth,
|
||||
hasAuth
|
||||
}: {
|
||||
authType: string | null | undefined;
|
||||
onAuthSubmit: (value: string) => void;
|
||||
onClearAuth: () => void;
|
||||
hasAuth: boolean;
|
||||
}) => {
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
|
|
@ -109,23 +117,39 @@ const AuthSection = ({
|
|||
|
||||
const handleModalCancel = () => setModalVisible(false);
|
||||
|
||||
const handleClearAuth = () => {
|
||||
onClearAuth();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Text className="text-sm font-medium text-gray-700">
|
||||
Authentication {hasAuth ? '✓' : ''}
|
||||
</Text>
|
||||
<Button
|
||||
onClick={handleAddAuth}
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="text-xs"
|
||||
>
|
||||
{hasAuth ? 'Update' : 'Add Auth'}
|
||||
</Button>
|
||||
<div className="flex gap-2">
|
||||
{hasAuth && (
|
||||
<Button
|
||||
onClick={handleClearAuth}
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="text-xs text-red-600 hover:text-red-700"
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
onClick={handleAddAuth}
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="text-xs"
|
||||
>
|
||||
{hasAuth ? 'Update' : 'Add Auth'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Text className="text-xs text-gray-500">
|
||||
{hasAuth ? 'Authentication configured' : 'Some tools may require authentication'}
|
||||
{hasAuth ? 'Authentication configured and saved locally' : 'Some tools may require authentication'}
|
||||
</Text>
|
||||
<AuthModal
|
||||
visible={modalVisible}
|
||||
|
|
@ -150,6 +174,32 @@ const MCPToolsViewer = ({
|
|||
const [toolResult, setToolResult] = useState<CallMCPToolResponse | null>(null);
|
||||
const [toolError, setToolError] = useState<Error | null>(null);
|
||||
|
||||
// Load stored auth token on component mount
|
||||
useEffect(() => {
|
||||
if (mcpServerHasAuth(auth_type)) {
|
||||
const storedAuthValue = getMCPAuthToken(serverId, serverAlias || undefined);
|
||||
if (storedAuthValue) {
|
||||
setMcpAuthValue(storedAuthValue);
|
||||
}
|
||||
}
|
||||
}, [serverId, serverAlias, auth_type]);
|
||||
|
||||
// Function to handle auth submission with localStorage persistence
|
||||
const handleAuthSubmit = (authValue: string) => {
|
||||
setMcpAuthValue(authValue);
|
||||
if (authValue && mcpServerHasAuth(auth_type)) {
|
||||
setMCPAuthToken(serverId, authValue, auth_type || 'none', serverAlias || undefined);
|
||||
message.success('Authentication token saved locally');
|
||||
}
|
||||
};
|
||||
|
||||
// Function to clear auth token
|
||||
const handleClearAuth = () => {
|
||||
setMcpAuthValue("");
|
||||
removeMCPAuthToken(serverId);
|
||||
message.info('Authentication token cleared');
|
||||
};
|
||||
|
||||
// Query to fetch MCP tools
|
||||
const { data: mcpToolsResponse, isLoading: isLoadingTools, error: mcpToolsError } = useQuery({
|
||||
queryKey: ["mcpTools", serverId, mcpAuthValue, serverAlias],
|
||||
|
|
@ -195,7 +245,7 @@ const MCPToolsViewer = ({
|
|||
<Title className="text-xl font-semibold mb-6 mt-2">MCP Tools</Title>
|
||||
|
||||
<div className="flex flex-col flex-1">
|
||||
{/* Tool Selection */}
|
||||
{/* Tool Selection - Show tools first */}
|
||||
<div className="flex flex-col flex-1 min-h-0">
|
||||
<Text className="font-medium block mb-3 text-gray-700 flex items-center">
|
||||
<ToolOutlined className="mr-2" /> Available Tools
|
||||
|
|
@ -295,17 +345,40 @@ const MCPToolsViewer = ({
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* Authentication Section */}
|
||||
{/* Authentication Section - Below tools list */}
|
||||
{mcpServerHasAuth(auth_type) && (
|
||||
<div className="pt-4 border-t border-gray-200 flex-shrink-0 mt-6">
|
||||
<Text className="font-medium block mb-3 text-gray-700 flex items-center">
|
||||
<SafetyOutlined className="mr-2" /> Authentication
|
||||
</Text>
|
||||
<AuthSection
|
||||
authType={auth_type}
|
||||
onAuthSubmit={(value) => setMcpAuthValue(value)}
|
||||
hasAuth={hasAuth}
|
||||
/>
|
||||
{!hasAuth ? (
|
||||
/* Prominent display when auth required but not provided */
|
||||
<div className="p-4 bg-gradient-to-r from-orange-50 to-red-50 border border-orange-200 rounded-lg">
|
||||
<div className="flex items-center mb-3">
|
||||
<SafetyOutlined className="mr-2 text-orange-600 text-lg" />
|
||||
<Text className="font-semibold text-orange-800">Authentication Required</Text>
|
||||
</div>
|
||||
<Text className="text-sm text-orange-700 mb-4">
|
||||
This MCP server requires authentication. You must add your credentials below to access the tools.
|
||||
</Text>
|
||||
<AuthSection
|
||||
authType={auth_type}
|
||||
onAuthSubmit={handleAuthSubmit}
|
||||
onClearAuth={handleClearAuth}
|
||||
hasAuth={hasAuth}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
/* Subtle display when already authenticated */
|
||||
<>
|
||||
<Text className="font-medium block mb-3 text-gray-700 flex items-center">
|
||||
<SafetyOutlined className="mr-2" /> Authentication
|
||||
</Text>
|
||||
<AuthSection
|
||||
authType={auth_type}
|
||||
onAuthSubmit={handleAuthSubmit}
|
||||
onClearAuth={handleClearAuth}
|
||||
hasAuth={hasAuth}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {
|
|||
import { clearTokenCookies } from "@/utils/cookieUtils"
|
||||
import { fetchProxySettings } from "@/utils/proxyUtils"
|
||||
import { useTheme } from "@/contexts/ThemeContext"
|
||||
import { clearMCPAuthTokens } from "./mcp_tools/mcp_auth_storage"
|
||||
|
||||
interface NavbarProps {
|
||||
userID: string | null;
|
||||
|
|
@ -65,6 +66,7 @@ const Navbar: React.FC<NavbarProps> = ({
|
|||
|
||||
const handleLogout = () => {
|
||||
clearTokenCookies();
|
||||
clearMCPAuthTokens(); // Clear MCP auth tokens on logout
|
||||
window.location.href = logoutUrl;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { Team } from "./key_team_helpers/key_list"
|
|||
import { jwtDecode } from "jwt-decode"
|
||||
import { Typography } from "antd"
|
||||
import { clearTokenCookies } from "@/utils/cookieUtils"
|
||||
import { clearMCPAuthTokens } from "./mcp_tools/mcp_auth_storage"
|
||||
|
||||
export interface ProxySettings {
|
||||
PROXY_BASE_URL: string | null
|
||||
|
|
@ -109,6 +110,8 @@ const UserDashboard: React.FC<UserDashboardProps> = ({
|
|||
window.addEventListener("beforeunload", function () {
|
||||
// Clear session storage
|
||||
sessionStorage.clear()
|
||||
// Note: MCP auth tokens are persistent and should not be cleared on page refresh
|
||||
// They are only cleared on logout
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue