mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-06 08:16:43 +00:00
(feat) Passthrough - set auth on passthrough endpoints, on the UI (#15778)
* fix(add_pass_through.tsx): allow setting 'auth' to true for passthrough endpoints on the UI * fix: working update auth on passthrough endpoints + show auth on passthrough table
This commit is contained in:
parent
02e34a57d6
commit
29a97784e7
9 changed files with 157 additions and 6 deletions
|
|
@ -3,5 +3,24 @@ model_list:
|
|||
litellm_params:
|
||||
model: bedrock/global.anthropic.claude-sonnet-4-5-20250929-v1:0
|
||||
|
||||
litellm_settings:
|
||||
callbacks: ["otel"]
|
||||
mcp_servers:
|
||||
github_mcp:
|
||||
url: "https://api.githubcopilot.com/mcp"
|
||||
auth_type: oauth2
|
||||
authorization_url: https://github.com/login/oauth/authorize
|
||||
token_url: https://github.com/login/oauth/access_token
|
||||
client_id: os.environ/GITHUB_OAUTH_CLIENT_ID
|
||||
client_secret: os.environ/GITHUB_OAUTH_CLIENT_SECRET
|
||||
scopes: ["public_repo", "user:email"]
|
||||
|
||||
general_settings:
|
||||
pass_through_endpoints:
|
||||
- path: "/fake-openai-proxy-10" # Route on LiteLLM Proxy
|
||||
target: "https://webhook.site/74bbcc59-a61f-4028-81e2-9e06814e81fe" # Target endpoint
|
||||
headers: # Headers to forward
|
||||
Authorization: "bearer sk-1234"
|
||||
content-type: application/json
|
||||
accept: application/json
|
||||
auth: true
|
||||
include_subpath: true
|
||||
cost_per_request: 0
|
||||
|
|
|
|||
|
|
@ -1628,6 +1628,10 @@ class PassThroughGenericEndpoint(LiteLLMPydanticObjectBase):
|
|||
default=0.0,
|
||||
description="The USD cost per request to the target endpoint. This is used to calculate the cost of the request to the target endpoint.",
|
||||
)
|
||||
auth: bool = Field(
|
||||
default=False,
|
||||
description="Whether authentication is required for the pass-through endpoint. If True, requests to the endpoint will require a valid LiteLLM API key.",
|
||||
)
|
||||
|
||||
|
||||
class PassThroughEndpointResponse(LiteLLMPydanticObjectBase):
|
||||
|
|
|
|||
|
|
@ -734,6 +734,7 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({
|
|||
userRole={userRole}
|
||||
userID={userID}
|
||||
modelData={modelData}
|
||||
premiumUser={premiumUser}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
|
|
|
|||
|
|
@ -450,6 +450,7 @@ export default function CreateKeyPage() {
|
|||
userRole={userRole}
|
||||
accessToken={accessToken}
|
||||
modelData={modelData}
|
||||
premiumUser={premiumUser}
|
||||
/>
|
||||
) : page == "logs" ? (
|
||||
<SpendLogsTable
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import KeyValueInput from "./key_value_input";
|
|||
import { passThroughItem } from "./pass_through_settings";
|
||||
import RoutePreview from "./route_preview";
|
||||
import NotificationsManager from "./molecules/notifications_manager";
|
||||
import PassThroughSecuritySection from "./common_components/PassThroughSecuritySection";
|
||||
const { Option } = Select2;
|
||||
|
||||
interface AddFallbacksProps {
|
||||
|
|
@ -33,12 +34,14 @@ interface AddFallbacksProps {
|
|||
accessToken: string;
|
||||
passThroughItems: passThroughItem[];
|
||||
setPassThroughItems: React.Dispatch<React.SetStateAction<passThroughItem[]>>;
|
||||
premiumUser?: boolean;
|
||||
}
|
||||
|
||||
const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
|
||||
accessToken,
|
||||
setPassThroughItems,
|
||||
passThroughItems,
|
||||
premiumUser = false,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
|
|
@ -47,7 +50,7 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
|
|||
const [pathValue, setPathValue] = useState("");
|
||||
const [targetValue, setTargetValue] = useState("");
|
||||
const [includeSubpath, setIncludeSubpath] = useState(true);
|
||||
|
||||
const [authEnabled, setAuthEnabled] = useState(false);
|
||||
const handleCancel = () => {
|
||||
form.resetFields();
|
||||
setPathValue("");
|
||||
|
|
@ -70,6 +73,10 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
|
|||
console.log("addPassThrough called with:", formValues);
|
||||
setIsLoading(true);
|
||||
try {
|
||||
// Remove auth field if not premium user
|
||||
if (!premiumUser && 'auth' in formValues) {
|
||||
delete formValues.auth;
|
||||
}
|
||||
console.log(`formValues: ${JSON.stringify(formValues)}`);
|
||||
|
||||
const response = await createPassThroughEndpoint(accessToken, formValues);
|
||||
|
|
@ -233,6 +240,15 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
|
|||
</Form.Item>
|
||||
</Card>
|
||||
|
||||
{/* Security Section */}
|
||||
<PassThroughSecuritySection
|
||||
premiumUser={premiumUser}
|
||||
authEnabled={authEnabled}
|
||||
onAuthChange={(checked) => {
|
||||
setAuthEnabled(checked);
|
||||
form.setFieldsValue({ auth: checked });
|
||||
}}
|
||||
/>
|
||||
{/* Billing Section */}
|
||||
<Card className="p-6">
|
||||
<Title className="text-lg font-semibold text-gray-900 mb-2">Billing</Title>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
import React from "react";
|
||||
import { Card, Title, Subtitle, Text } from "@tremor/react";
|
||||
import { Form, Switch } from "antd";
|
||||
|
||||
export interface PassThroughSecuritySectionProps {
|
||||
premiumUser: boolean;
|
||||
authEnabled: boolean;
|
||||
onAuthChange: (checked: boolean) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reusable Security section for pass-through endpoints
|
||||
* Shows authentication toggle for premium users or upgrade message for free users
|
||||
*/
|
||||
const PassThroughSecuritySection: React.FC<PassThroughSecuritySectionProps> = ({
|
||||
premiumUser,
|
||||
authEnabled,
|
||||
onAuthChange,
|
||||
}) => {
|
||||
return (
|
||||
<Card className="p-6">
|
||||
<Title className="text-lg font-semibold text-gray-900 mb-2">Security</Title>
|
||||
<Subtitle className="text-gray-600 mb-4">
|
||||
When enabled, requests to this endpoint will require a valid LiteLLM API key
|
||||
</Subtitle>
|
||||
{premiumUser ? (
|
||||
<Form.Item name="auth" valuePropName="checked" className="mb-0">
|
||||
<Switch
|
||||
checked={authEnabled}
|
||||
onChange={(checked) => {
|
||||
onAuthChange(checked);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
) : (
|
||||
<div>
|
||||
<div className="flex items-center mb-3">
|
||||
<Switch
|
||||
disabled
|
||||
checked={false}
|
||||
style={{ outline: '2px solid #d1d5db', outlineOffset: '2px' }}
|
||||
/>
|
||||
<span className="ml-2 text-sm text-gray-400">Authentication (Premium)</span>
|
||||
</div>
|
||||
<div className="p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
|
||||
<Text className="text-sm text-yellow-800">
|
||||
Setting authentication for pass-through endpoints is a LiteLLM Enterprise feature. Get a trial key{" "}
|
||||
<a
|
||||
href="https://www.litellm.ai/#pricing"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline"
|
||||
>
|
||||
here
|
||||
</a>
|
||||
.
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default PassThroughSecuritySection;
|
||||
|
||||
|
|
@ -18,12 +18,14 @@ import { updatePassThroughEndpoint, deletePassThroughEndpointsCall } from "./net
|
|||
import { Eye, EyeOff } from "lucide-react";
|
||||
import RoutePreview from "./route_preview";
|
||||
import NotificationsManager from "./molecules/notifications_manager";
|
||||
import PassThroughSecuritySection from "./common_components/PassThroughSecuritySection";
|
||||
|
||||
export interface PassThroughInfoProps {
|
||||
endpointData: PassThroughEndpoint;
|
||||
onClose: () => void;
|
||||
accessToken: string | null;
|
||||
isAdmin: boolean;
|
||||
premiumUser?: boolean;
|
||||
onEndpointUpdated?: () => void;
|
||||
}
|
||||
|
||||
|
|
@ -34,6 +36,7 @@ interface PassThroughEndpoint {
|
|||
headers: Record<string, any>;
|
||||
include_subpath?: boolean;
|
||||
cost_per_request?: number;
|
||||
auth?: boolean;
|
||||
}
|
||||
|
||||
// Password field component for headers
|
||||
|
|
@ -58,11 +61,13 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
|
|||
onClose,
|
||||
accessToken,
|
||||
isAdmin,
|
||||
premiumUser = false,
|
||||
onEndpointUpdated,
|
||||
}) => {
|
||||
const [endpointData, setEndpointData] = useState<PassThroughEndpoint | null>(initialEndpointData);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [authEnabled, setAuthEnabled] = useState(initialEndpointData?.auth || false);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const handleEndpointUpdate = async (values: any) => {
|
||||
|
|
@ -86,6 +91,7 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
|
|||
headers: headers,
|
||||
include_subpath: values.include_subpath,
|
||||
cost_per_request: values.cost_per_request,
|
||||
auth: premiumUser ? values.auth : undefined,
|
||||
};
|
||||
|
||||
await updatePassThroughEndpoint(accessToken, endpointData.id, updateData);
|
||||
|
|
@ -174,6 +180,11 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
|
|||
{endpointData.include_subpath ? "Include Subpath" : "Exact Path"}
|
||||
</Badge>
|
||||
</div>
|
||||
<div>
|
||||
<Badge color={endpointData.auth ? "blue" : "gray"}>
|
||||
{endpointData.auth ? "Auth Required" : "No Auth"}
|
||||
</Badge>
|
||||
</div>
|
||||
{endpointData.cost_per_request !== undefined && (
|
||||
<div>
|
||||
<Text>Cost per request: ${endpointData.cost_per_request}</Text>
|
||||
|
|
@ -232,6 +243,7 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
|
|||
headers: endpointData.headers ? JSON.stringify(endpointData.headers, null, 2) : "",
|
||||
include_subpath: endpointData.include_subpath || false,
|
||||
cost_per_request: endpointData.cost_per_request,
|
||||
auth: endpointData.auth || false,
|
||||
}}
|
||||
layout="vertical"
|
||||
>
|
||||
|
|
@ -258,6 +270,15 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
|
|||
<InputNumber min={0} step={0.01} precision={2} placeholder="0.00" addonBefore="$" />
|
||||
</Form.Item>
|
||||
|
||||
<PassThroughSecuritySection
|
||||
premiumUser={premiumUser}
|
||||
authEnabled={authEnabled}
|
||||
onAuthChange={(checked) => {
|
||||
setAuthEnabled(checked);
|
||||
form.setFieldsValue({ auth: checked });
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="flex justify-end gap-2 mt-6">
|
||||
<Button onClick={() => setIsEditing(false)}>Cancel</Button>
|
||||
<TremorButton>Save Changes</TremorButton>
|
||||
|
|
@ -285,6 +306,12 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
|
|||
<div>${endpointData.cost_per_request}</div>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<Text className="font-medium">Authentication Required</Text>
|
||||
<Badge color={endpointData.auth ? "green" : "gray"}>
|
||||
{endpointData.auth ? "Yes" : "No"}
|
||||
</Badge>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="font-medium">Headers</Text>
|
||||
{endpointData.headers && Object.keys(endpointData.headers).length > 0 ? (
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { Text, Button, Icon, Title } from "@tremor/react";
|
||||
import { deletePassThroughEndpointsCall, getPassThroughEndpointsCall } from "./networking";
|
||||
import { Tooltip } from "antd";
|
||||
import { PencilAltIcon, TrashIcon } from "@heroicons/react/outline";
|
||||
import { Badge, Tooltip } from "antd";
|
||||
import { PencilAltIcon, TrashIcon, InformationCircleIcon } from "@heroicons/react/outline";
|
||||
import AddPassThroughEndpoint from "./add_pass_through";
|
||||
import PassThroughInfoView from "./pass_through_info";
|
||||
import { DataTable } from "./view_logs/table";
|
||||
|
|
@ -15,6 +15,7 @@ interface GeneralSettingsPageProps {
|
|||
userRole: string | null;
|
||||
userID: string | null;
|
||||
modelData: any;
|
||||
premiumUser?: boolean;
|
||||
}
|
||||
|
||||
interface routingStrategyArgs {
|
||||
|
|
@ -37,6 +38,7 @@ export interface passThroughItem {
|
|||
headers: object;
|
||||
include_subpath?: boolean;
|
||||
cost_per_request?: number;
|
||||
auth?: boolean;
|
||||
}
|
||||
|
||||
// Password field component for headers
|
||||
|
|
@ -54,7 +56,7 @@ const PasswordField: React.FC<{ value: object }> = ({ value }) => {
|
|||
);
|
||||
};
|
||||
|
||||
const PassThroughSettings: React.FC<GeneralSettingsPageProps> = ({ accessToken, userRole, userID, modelData }) => {
|
||||
const PassThroughSettings: React.FC<GeneralSettingsPageProps> = ({ accessToken, userRole, userID, modelData, premiumUser }) => {
|
||||
const [generalSettings, setGeneralSettings] = useState<passThroughItem[]>([]);
|
||||
const [selectedEndpointId, setSelectedEndpointId] = useState<string | null>(null);
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
|
|
@ -144,6 +146,18 @@ const PassThroughSettings: React.FC<GeneralSettingsPageProps> = ({ accessToken,
|
|||
accessorKey: "target",
|
||||
cell: (info: any) => <Text>{info.getValue()}</Text>,
|
||||
},
|
||||
{
|
||||
header: () => (
|
||||
<div className="flex items-center gap-1">
|
||||
<span>Authentication</span>
|
||||
<Tooltip title="LiteLLM Virtual Key required to call endpoint">
|
||||
<InformationCircleIcon className="w-4 h-4 text-gray-400 cursor-help" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
),
|
||||
accessorKey: "auth",
|
||||
cell: (info: any) => <Badge color={info.getValue() ? "green" : "gray"}>{info.getValue() ? "Yes" : "No"}</Badge>,
|
||||
},
|
||||
{
|
||||
header: "Headers",
|
||||
accessorKey: "headers",
|
||||
|
|
@ -192,6 +206,7 @@ const PassThroughSettings: React.FC<GeneralSettingsPageProps> = ({ accessToken,
|
|||
onClose={() => setSelectedEndpointId(null)}
|
||||
accessToken={accessToken}
|
||||
isAdmin={userRole === "Admin" || userRole === "admin"}
|
||||
premiumUser={premiumUser}
|
||||
onEndpointUpdated={handleEndpointUpdated}
|
||||
/>
|
||||
);
|
||||
|
|
@ -208,6 +223,7 @@ const PassThroughSettings: React.FC<GeneralSettingsPageProps> = ({ accessToken,
|
|||
accessToken={accessToken}
|
||||
setPassThroughItems={setGeneralSettings}
|
||||
passThroughItems={generalSettings}
|
||||
premiumUser={premiumUser}
|
||||
/>
|
||||
|
||||
<DataTable
|
||||
|
|
|
|||
|
|
@ -1364,6 +1364,7 @@ const OldModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
userRole={userRole}
|
||||
userID={userID}
|
||||
modelData={modelData}
|
||||
premiumUser={premiumUser}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue