(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:
Krish Dholakia 2025-10-21 23:26:43 -07:00 committed by GitHub
parent 02e34a57d6
commit 29a97784e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 157 additions and 6 deletions

View file

@ -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

View file

@ -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):

View file

@ -734,6 +734,7 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({
userRole={userRole}
userID={userID}
modelData={modelData}
premiumUser={premiumUser}
/>
</TabPanel>
<TabPanel>

View file

@ -450,6 +450,7 @@ export default function CreateKeyPage() {
userRole={userRole}
accessToken={accessToken}
modelData={modelData}
premiumUser={premiumUser}
/>
) : page == "logs" ? (
<SpendLogsTable

View file

@ -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>

View file

@ -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;

View file

@ -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 ? (

View file

@ -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

View file

@ -1364,6 +1364,7 @@ const OldModelDashboard: React.FC<ModelDashboardProps> = ({
userRole={userRole}
userID={userID}
modelData={modelData}
premiumUser={premiumUser}
/>
</TabPanel>
<TabPanel>