From f966cce26ab660293c673293561d91e40f1c04fc Mon Sep 17 00:00:00 2001 From: ishaan-jaff Date: Tue, 30 Jan 2024 16:47:12 -0800 Subject: [PATCH 1/5] (feat) add litellm login to proxy --- litellm/proxy/proxy_server.py | 78 +++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index f1ec2744cd..3ed91684a3 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -3103,6 +3103,84 @@ async def user_info( ) +html_form = """ + + + + LiteLLM Login + + + +
+

LiteLLM Login

+ + + + + +
+ + +""" +from fastapi import FastAPI, Form +from fastapi.responses import HTMLResponse + + +@router.get("/login/page") +async def login_page(): + return HTMLResponse(content=html_form, status_code=200) + + +@router.get("/login") +async def login(username: str = Form(...), password: str = Form(...)): + # Here you can perform authentication logic + # For simplicity, let's just print the received credentials + # print(f"Received username: {username}, password: {password}") + return {"message": "Login successful"} + + @router.post( "/user/update", tags=["user management"], dependencies=[Depends(user_api_key_auth)] ) From 8daabe16f4f282e6419ead7a3e3fbc0d7a4bcce4 Mon Sep 17 00:00:00 2001 From: ishaan-jaff Date: Tue, 30 Jan 2024 17:00:40 -0800 Subject: [PATCH 2/5] (feat) allow users to use UI without SSO --- litellm/proxy/proxy_server.py | 126 +++++++++++++--------------------- litellm/proxy/utils.py | 64 +++++++++++++++++ 2 files changed, 112 insertions(+), 78 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 3ed91684a3..9bd33413c5 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -76,6 +76,7 @@ from litellm.proxy.utils import ( get_logging_payload, reset_budget, hash_token, + html_form, ) from litellm.proxy.secret_managers.google_kms import load_google_kms import pydantic @@ -94,6 +95,7 @@ from fastapi import ( BackgroundTasks, Header, Response, + Form, ) from fastapi.routing import APIRouter from fastapi.security import OAuth2PasswordBearer @@ -2958,6 +2960,52 @@ async def google_login(request: Request): ) with microsoft_sso: return await microsoft_sso.get_login_redirect() + else: + # No Google, Microsoft SSO + # Use UI Credentials set in .env + from fastapi.responses import HTMLResponse + + return HTMLResponse(content=html_form, status_code=200) + + +@router.post( + "/login", include_in_schema=False +) # hidden since this is a helper for UI sso login +async def login(username: str = Form(...), password: str = Form(...)): + ui_username = os.getenv("UI_USERNAME") + ui_password = os.getenv("UI_PASSWORD") + + if username == ui_username and password == ui_password: + user_id = username + response = await generate_key_helper_fn( + **{"duration": "24hr", "models": [], "aliases": {}, "config": {}, "spend": 0, "user_id": user_id, "team_id": "litellm-dashboard"} # type: ignore + ) + + key = response["token"] # type: ignore + user_id = response["user_id"] # type: ignore + litellm_dashboard_ui = "https://litellm-dashboard.vercel.app/" + + # if user set LITELLM_UI_LINK in .env, use that + litellm_ui_link_in_env = os.getenv("LITELLM_UI_LINK", None) + if litellm_ui_link_in_env is not None: + litellm_dashboard_ui = litellm_ui_link_in_env + + litellm_dashboard_ui += ( + "?userID=" + + user_id + + "&accessToken=" + + key + + "&proxyBaseUrl=" + + os.getenv("PROXY_BASE_URL") + ) + return RedirectResponse(url=litellm_dashboard_ui) + else: + raise ProxyException( + message=f"Invalid credentials used to access UI. Passed in username: {username}, passed in password: {password}.\nCheck 'UI_USERNAME', 'UI_PASSWORD' in .env file", + type="auth_error", + param="invalid_credentials", + code=status.HTTP_401_UNAUTHORIZED, + ) @app.get("/sso/callback", tags=["experimental"]) @@ -3103,84 +3151,6 @@ async def user_info( ) -html_form = """ - - - - LiteLLM Login - - - -
-

LiteLLM Login

- - - - - -
- - -""" -from fastapi import FastAPI, Form -from fastapi.responses import HTMLResponse - - -@router.get("/login/page") -async def login_page(): - return HTMLResponse(content=html_form, status_code=200) - - -@router.get("/login") -async def login(username: str = Form(...), password: str = Form(...)): - # Here you can perform authentication logic - # For simplicity, let's just print the received credentials - # print(f"Received username: {username}, password: {password}") - return {"message": "Login successful"} - - @router.post( "/user/update", tags=["user management"], dependencies=[Depends(user_api_key_auth)] ) diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index 3ec45203f5..d9194e712b 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -1208,3 +1208,67 @@ async def reset_budget(prisma_client: PrismaClient): await prisma_client.update_data( query_type="update_many", data_list=users_to_reset, table_name="user" ) + + +# LiteLLM Admin UI - Non SSO Login +html_form = """ + + + + LiteLLM Login + + + +
+

LiteLLM Login

+ + + + + +
+ + +""" From cfa69c31847bb39a15e8eed1c24c750a884d8872 Mon Sep 17 00:00:00 2001 From: ishaan-jaff Date: Tue, 30 Jan 2024 17:05:36 -0800 Subject: [PATCH 3/5] (docs) UI - no sso --- docs/my-website/docs/proxy/ui.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/my-website/docs/proxy/ui.md b/docs/my-website/docs/proxy/ui.md index 0a19c427c2..6538793c1f 100644 --- a/docs/my-website/docs/proxy/ui.md +++ b/docs/my-website/docs/proxy/ui.md @@ -31,6 +31,18 @@ general_settings: ## 2. Setup SSO/Auth for UI + + +Set the following in your .env on the Proxy + +```shell +UI_USERNAME=ishaan-litellm +UI_PASSWORD=langchain +``` + +On accessing the LiteLLM UI, you will be prompted to enter your username, password + + @@ -73,6 +85,7 @@ MICROSOFT_TENANT="5a39737 ``` + ## 4. Use UI From 97805891732e34164a1fde20b2dc112e2de1e661 Mon Sep 17 00:00:00 2001 From: ishaan-jaff Date: Tue, 30 Jan 2024 18:13:53 -0800 Subject: [PATCH 4/5] (fix) dependencies in /sso/key/generate --- litellm/proxy/proxy_server.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 9bd33413c5..4b13160d77 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -2971,7 +2971,10 @@ async def google_login(request: Request): @router.post( "/login", include_in_schema=False ) # hidden since this is a helper for UI sso login -async def login(username: str = Form(...), password: str = Form(...)): +async def login(request: Request): + form = await request.form() + username = str(form.get("username")) + password = form.get("password") ui_username = os.getenv("UI_USERNAME") ui_password = os.getenv("UI_PASSWORD") From 069976daed3e4d410cdd4874dc6db1d4ec783d8b Mon Sep 17 00:00:00 2001 From: ishaan-jaff Date: Tue, 30 Jan 2024 18:19:49 -0800 Subject: [PATCH 5/5] (fix) install python-multipart if missing --- litellm/proxy/proxy_server.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 4b13160d77..edd4232f73 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -2972,6 +2972,11 @@ async def google_login(request: Request): "/login", include_in_schema=False ) # hidden since this is a helper for UI sso login async def login(request: Request): + try: + import multipart + except ImportError: + subprocess.run(["pip", "install", "python-multipart"]) + form = await request.form() username = str(form.get("username")) password = form.get("password")