diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/panels/PassThroughPanel.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/panels/PassThroughPanel.tsx index 7ae7e976651..ad08ff48a5b 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/panels/PassThroughPanel.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/panels/PassThroughPanel.tsx @@ -4,8 +4,6 @@ import PassThroughSettings from "@/components/PassThroughSettings/PassThroughSet import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; export default function PassThroughPanel() { - const { accessToken, userRole, userId: userID, premiumUser } = useAuthorized(); - return ( - - ); + const { accessToken, userRole, userId: userID } = useAuthorized(); + return ; } diff --git a/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughSettings.tsx b/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughSettings.tsx index 8ef2766d412..e45954d7c11 100644 --- a/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughSettings.tsx +++ b/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughSettings.tsx @@ -19,7 +19,6 @@ interface PassThroughSettingsProps { accessToken: string | null; userRole: string | null; userID: string | null; - premiumUser?: boolean; } export interface passThroughItem { @@ -37,7 +36,7 @@ export interface passThroughItem { is_from_config?: boolean; } -const PassThroughSettings: React.FC = ({ accessToken, userRole, userID, premiumUser }) => { +const PassThroughSettings: React.FC = ({ accessToken, userRole, userID }) => { const [generalSettings, setGeneralSettings] = useState([]); const [isLoading, setIsLoading] = useState(true); const [selectedEndpointId, setSelectedEndpointId] = useState(null); @@ -116,7 +115,6 @@ const PassThroughSettings: React.FC = ({ accessToken, onClose={() => setSelectedEndpointId(null)} accessToken={accessToken} isAdmin={userRole === "Admin" || userRole === "admin"} - premiumUser={premiumUser} onEndpointUpdated={handleEndpointUpdated} /> ); @@ -133,7 +131,6 @@ const PassThroughSettings: React.FC = ({ accessToken, accessToken={accessToken} setPassThroughItems={setGeneralSettings} passThroughItems={generalSettings} - premiumUser={premiumUser} /> userEvent.setup({ pointerEventsCheck: PointerEventsCheckLeve type User = ReturnType; -const renderForm = (premiumUser = true) => +const renderForm = () => renderWithProviders( - , + , ); const openModal = async (user: User) => { @@ -82,7 +77,7 @@ const reopenedPayload = { include_subpath: true, methods: undefined, default_query_params: undefined, - auth: undefined, + auth: true, timeout: undefined, cost_per_request: undefined, }; @@ -93,7 +88,7 @@ describe("add_pass_through submit payload", () => { createPassThroughEndpoint.mockResolvedValue({ endpoints: [{ id: "generated-id", path: "/bria" }] }); }); - it("sends exactly the antd payload, once, for a minimal endpoint", async () => { + it("sends the configured headers and numeric fields once", async () => { const user = setup(); renderForm(); await openModal(user); @@ -114,7 +109,7 @@ describe("add_pass_through submit payload", () => { include_subpath: true, methods: undefined, default_query_params: undefined, - auth: undefined, + auth: true, timeout: "900", cost_per_request: "1.5", }); @@ -161,32 +156,37 @@ describe("add_pass_through submit payload", () => { expect(lastPayload().methods).toStrictEqual(["POST"]); }); - it("sends auth true once the premium security toggle is switched on", async () => { + it("creates an authenticated endpoint without adding headers", async () => { const user = setup(); - renderForm(true); + renderForm(); await openModal(user); - await fillRequiredFields(user); - - const switches = screen.getAllByRole("switch"); - expect(switches).toHaveLength(2); - await user.click(switches[1]); + fireEvent.change(screen.getByPlaceholderText("bria"), { target: { value: "example" } }); + fireEvent.change(screen.getByPlaceholderText("https://engine.prod.bria-api.com"), { + target: { value: "https://example.com" }, + }); + expect(screen.getByRole("switch", { name: "Require LiteLLM authentication" })).toBeChecked(); await submit(user); - await waitFor(() => expect(createPassThroughEndpoint).toHaveBeenCalled()); - expect(lastPayload().auth).toBe(true); + await waitFor(() => expect(createPassThroughEndpoint).toHaveBeenCalledOnce()); + expect(lastPayload()).toMatchObject({ path: "/example", headers: {}, auth: true }); }); - it("omits the auth key entirely for a non-premium user", async () => { + it("saves authentication off with all headers removed and resets it on reopening", async () => { const user = setup(); - renderForm(false); + renderForm(); await openModal(user); await fillRequiredFields(user); + await user.click(screen.getByRole("button", { name: "Remove header 1" })); + await user.click(screen.getByRole("switch", { name: "Require LiteLLM authentication" })); await submit(user); - await waitFor(() => expect(createPassThroughEndpoint).toHaveBeenCalled()); - expect(lastPayload()).not.toHaveProperty("auth"); + await waitFor(() => expect(createPassThroughEndpoint).toHaveBeenCalledOnce()); + expect(lastPayload()).toMatchObject({ headers: {}, auth: false }); + await openModal(user); + expect(screen.getByRole("switch", { name: "Require LiteLLM authentication" })).toBeChecked(); + expect(screen.queryByPlaceholderText("Header Name")).not.toBeInTheDocument(); }); it("drops include_subpath to false when the toggle is switched off", async () => { @@ -212,7 +212,6 @@ describe("add_pass_through submit payload", () => { expect(await screen.findByText("Path is required")).toBeInTheDocument(); expect(screen.getByText("Target URL is required")).toBeInTheDocument(); - expect(screen.getByText("Please configure the headers")).toBeInTheDocument(); expect(createPassThroughEndpoint).not.toHaveBeenCalled(); }); diff --git a/ui/litellm-dashboard/src/components/add_pass_through.tsx b/ui/litellm-dashboard/src/components/add_pass_through.tsx index a240f1f6bce..10a1e1a90ac 100644 --- a/ui/litellm-dashboard/src/components/add_pass_through.tsx +++ b/ui/litellm-dashboard/src/components/add_pass_through.tsx @@ -41,11 +41,9 @@ const passThroughFormSchema = z.object({ .pipe(z.url({ error: "Please enter a valid URL" })), methods: z.array(z.string()).optional(), include_subpath: z.boolean(), - headers: keyValuePairsSchema.refine((pairs) => pairs.some(([name]) => name !== ""), { - error: "Please configure the headers", - }), + headers: keyValuePairsSchema, default_query_params: keyValuePairsSchema.optional(), - auth: z.boolean().optional(), + auth: z.boolean(), timeout: z.string().optional(), cost_per_request: z.string().optional(), }); @@ -59,7 +57,7 @@ const emptyFormValues = { include_subpath: true, headers: [], default_query_params: undefined, - auth: undefined, + auth: true, timeout: undefined, cost_per_request: undefined, } as unknown as PassThroughFormValues; @@ -88,14 +86,12 @@ interface AddFallbacksProps { accessToken: string; passThroughItems: passThroughItem[]; setPassThroughItems: React.Dispatch>; - premiumUser?: boolean; } const AddPassThroughEndpoint: React.FC = ({ accessToken, setPassThroughItems, passThroughItems, - premiumUser = false, }) => { const [isModalVisible, setIsModalVisible] = useState(false); const [isLoading, setIsLoading] = useState(false); @@ -123,7 +119,7 @@ const AddPassThroughEndpoint: React.FC = ({ include_subpath: values.include_subpath, headers: toRecord(values.headers), default_query_params: optionalRecord(values.default_query_params), - ...(premiumUser ? { auth: values.auth } : {}), + auth: values.auth, timeout: values.timeout, cost_per_request: values.cost_per_request, ...(Object.keys(guardrails).length > 0 ? { guardrails } : {}), @@ -268,14 +264,12 @@ const AddPassThroughEndpoint: React.FC = ({ control={form.control} name="headers" label={labelWithHint( - "Authentication Headers", + "Headers (Optional)", "Authentication and other headers to forward with requests", )} description={ <> - - Add authentication tokens and other required headers - + Add headers only if required by the target API Common examples: auth_token, Authorization, x-api-key } @@ -313,13 +307,7 @@ const AddPassThroughEndpoint: React.FC = ({ - {({ value, onChange }) => ( - - )} + {({ value, onChange }) => } diff --git a/ui/litellm-dashboard/src/components/common_components/PassThroughSecuritySection.tsx b/ui/litellm-dashboard/src/components/common_components/PassThroughSecuritySection.tsx index c7aac09fd04..c6c167ce9f0 100644 --- a/ui/litellm-dashboard/src/components/common_components/PassThroughSecuritySection.tsx +++ b/ui/litellm-dashboard/src/components/common_components/PassThroughSecuritySection.tsx @@ -4,41 +4,18 @@ import { Card } from "@/components/ui/card"; import { Switch } from "@/components/ui/switch"; export interface PassThroughSecuritySectionProps { - premiumUser: boolean; authEnabled: boolean; onAuthChange: (checked: boolean) => void; } -const PassThroughSecuritySection: React.FC = ({ - premiumUser, - authEnabled, - onAuthChange, -}) => { +const PassThroughSecuritySection: React.FC = ({ authEnabled, onAuthChange }) => { return (

Security

When enabled, requests to this endpoint will require a valid LiteLLM Virtual Key

- {premiumUser ? ( - - ) : ( -
-
- - Authentication (Premium) -
-
-

- Setting authentication for pass-through endpoints is a LiteLLM Enterprise feature. Get a trial key{" "} - - here - - . -

-
-
- )} +
); }; diff --git a/ui/litellm-dashboard/src/components/pass_through_info.integration.test.tsx b/ui/litellm-dashboard/src/components/pass_through_info.integration.test.tsx index 90994a8a2cd..051195c72b4 100644 --- a/ui/litellm-dashboard/src/components/pass_through_info.integration.test.tsx +++ b/ui/litellm-dashboard/src/components/pass_through_info.integration.test.tsx @@ -31,16 +31,8 @@ const endpoint = { methods: ["GET"], }; -const renderView = (premiumUser = true, data = endpoint) => - renderWithProviders( - , - ); +const renderView = (data = endpoint) => + renderWithProviders(); const openEditForm = async (user: User) => { await user.click(screen.getByRole("tab", { name: "Settings" })); @@ -122,20 +114,34 @@ describe("pass_through_info update payload", () => { expect(lastPayload().headers).toStrictEqual({ "x-api-key": "k" }); }); - it("sends auth undefined for a non-premium user", async () => { + it.each([true, false])("loads auth %s and persists the opposite choice", async (auth) => { const user = setup(); - renderView(false); + renderView({ ...endpoint, headers: {}, auth }); await openEditForm(user); + const toggle = screen.getByRole("switch", { name: "Require LiteLLM authentication" }); + if (auth) { + expect(toggle).toBeChecked(); + } else { + expect(toggle).not.toBeChecked(); + } + await user.click(toggle); await save(user); - await waitFor(() => expect(updatePassThroughEndpoint).toHaveBeenCalled()); - expect(lastPayload().auth).toBeUndefined(); + await waitFor(() => expect(updatePassThroughEndpoint).toHaveBeenCalledOnce()); + expect(lastPayload()).toMatchObject({ headers: {}, auth: !auth }); + await openEditForm(user); + const savedToggle = screen.getByRole("switch", { name: "Require LiteLLM authentication" }); + if (auth) { + expect(savedToggle).not.toBeChecked(); + } else { + expect(savedToggle).toBeChecked(); + } }); it("sends methods undefined when the endpoint has none", async () => { const user = setup(); - renderView(true, { ...endpoint, methods: [] }); + renderView({ ...endpoint, methods: [] }); await openEditForm(user); await save(user); diff --git a/ui/litellm-dashboard/src/components/pass_through_info.tsx b/ui/litellm-dashboard/src/components/pass_through_info.tsx index f44afdd9bf8..3925443fa86 100644 --- a/ui/litellm-dashboard/src/components/pass_through_info.tsx +++ b/ui/litellm-dashboard/src/components/pass_through_info.tsx @@ -91,7 +91,6 @@ export interface PassThroughInfoProps { onClose: () => void; accessToken: string | null; isAdmin: boolean; - premiumUser?: boolean; onEndpointUpdated?: () => void; } @@ -139,7 +138,6 @@ const PassThroughInfoView: React.FC = ({ onClose, accessToken, isAdmin, - premiumUser = false, onEndpointUpdated, }) => { const [endpointData, setEndpointData] = useState(initialEndpointData); @@ -157,7 +155,7 @@ const PassThroughInfoView: React.FC = ({ include_subpath: initialEndpointData.include_subpath || false, cost_per_request: initialEndpointData.cost_per_request, timeout: initialEndpointData.timeout, - auth: initialEndpointData.auth || false, + auth: initialEndpointData.auth ?? true, }, }); @@ -189,7 +187,7 @@ const PassThroughInfoView: React.FC = ({ include_subpath: values.include_subpath, cost_per_request: values.cost_per_request, timeout: values.timeout, - auth: premiumUser ? values.auth : undefined, + auth: values.auth, methods: values.methods.length > 0 ? values.methods : undefined, guardrails: guardrails && Object.keys(guardrails).length > 0 ? guardrails : undefined, }; @@ -472,11 +470,7 @@ const PassThroughInfoView: React.FC = ({ {({ value, onChange }) => ( - + )}