diff --git a/litellm/proxy/management_endpoints/ui_sso.py b/litellm/proxy/management_endpoints/ui_sso.py index e2836ca191f..9fdb05c4bc9 100644 --- a/litellm/proxy/management_endpoints/ui_sso.py +++ b/litellm/proxy/management_endpoints/ui_sso.py @@ -72,11 +72,13 @@ router = APIRouter() @router.get("/sso/key/generate", tags=["experimental"], include_in_schema=False) -async def google_login(request: Request, source: Optional[str] = None, key: Optional[str] = None): # noqa: PLR0915 +async def serve_login_page(request: Request, source: Optional[str] = None, key: Optional[str] = None, error: Optional[str] = None): """ Create Proxy API Keys using Google Workspace SSO. Requires setting PROXY_BASE_URL in .env PROXY_BASE_URL should be the your deployed proxy endpoint, e.g. PROXY_BASE_URL="https://litellm-production-7002.up.railway.app/" Example: + Serves a unified login page with options for both normal + username/password login and SSO. """ from litellm.proxy.proxy_server import ( premium_user, @@ -94,6 +96,316 @@ async def google_login(request: Request, source: Optional[str] = None, key: Opti if is_disabled: return admin_ui_disabled() + ####### Check if user is a Enterprise / Premium User for SSO ####### + sso_available = False + if ( + microsoft_client_id is not None + or google_client_id is not None + or generic_client_id is not None + ): + if premium_user is True: + sso_available = True + + ####### Detect DB + MASTER KEY in .env ####### + missing_env_vars = show_missing_vars_in_env() + if missing_env_vars is not None: + return missing_env_vars + + # Build the unified login page HTML + error_message = "" + if error == "1": + error_message = """ +
+ ⚠️ Invalid username or password. Please try again. +
+ """ + + sso_button = "" + if sso_available: + sso_button = """ +
+

or

+ + 🔐 Login with SSO + +
+ """ + + # Get the base URL for form action - CHANGE THIS TO POINT TO /login + proxy_base_url = os.getenv("PROXY_BASE_URL", "") + server_root_path = os.getenv("SERVER_ROOT_PATH", "") + if server_root_path != "": + proxy_base_url += server_root_path + form_action = proxy_base_url + "/sso/key/generate" # CHANGE BACK to /sso/key/generate + + unified_login_html = f""" + + + + + LiteLLM Login + + + + +
+
+ +
+

Login

+

Access your LiteLLM Admin UI.

+ + {error_message} + +
+
+ + + + + + Default Credentials +
+

By default, Username is admin and Password is your set LiteLLM Proxy MASTER_KEY.

+

Need to set UI credentials or SSO? Check the documentation.

+
+ + + + + + +
+ + +
+ + + {sso_button} +
+ + + + """ + + from fastapi.responses import HTMLResponse + return HTMLResponse(content=unified_login_html, status_code=200) + + +@router.get("/sso/login", tags=["experimental"], include_in_schema=False) +async def sso_login_redirect(request: Request, source: Optional[str] = None, key: Optional[str] = None): + """ + Handles SSO login redirect - this is what the "Login with SSO" button points to + """ + from litellm.proxy.proxy_server import premium_user, user_custom_ui_sso_sign_in_handler + + microsoft_client_id = os.getenv("MICROSOFT_CLIENT_ID", None) + google_client_id = os.getenv("GOOGLE_CLIENT_ID", None) + generic_client_id = os.getenv("GENERIC_CLIENT_ID", None) + ####### Check if user is a Enterprise / Premium User ####### if ( microsoft_client_id is not None @@ -108,12 +420,6 @@ async def google_login(request: Request, source: Optional[str] = None, key: Opti code=status.HTTP_403_FORBIDDEN, ) - ####### Detect DB + MASTER KEY in .env ####### - missing_env_vars = show_missing_vars_in_env() - if missing_env_vars is not None: - return missing_env_vars - ui_username = os.getenv("UI_USERNAME") - # get url from request - always use regular callback, but set state for CLI redirect_url = SSOAuthenticationHandler.get_redirect_url_for_sso( request=request, @@ -155,16 +461,9 @@ async def google_login(request: Request, source: Optional[str] = None, key: Opti generic_client_id=generic_client_id, state=cli_state, ) - elif ui_username is not None: - # No Google, Microsoft SSO - # Use UI Credentials set in .env - from fastapi.responses import HTMLResponse - - return HTMLResponse(content=html_form, status_code=200) else: - from fastapi.responses import HTMLResponse - - return HTMLResponse(content=html_form, status_code=200) + # No SSO configured, redirect back to login page + return RedirectResponse(url="/sso/key/generate", status_code=303) def generic_response_convertor( @@ -1889,3 +2188,28 @@ async def debug_sso_callback(request: Request): ) return HTMLResponse(content=html_content) + + +@router.post("/sso/key/generate", tags=["experimental"], include_in_schema=False) +async def process_login(request: Request): + """ + Process username/password login from the unified login page + """ + try: + # Get form data + form_data = await request.form() + username = form_data.get("username") + password = form_data.get("password") + + if not username or not password: + return RedirectResponse(url="/sso/key/generate?error=1", status_code=303) + + # Import the actual login function from proxy_server + from litellm.proxy.proxy_server import login + + # Call the real login function that handles all the authentication properly + return await login(request) + + except Exception as e: + verbose_proxy_logger.error(f"Error processing login: {e}") + return RedirectResponse(url="/sso/key/generate?error=1", status_code=303)