mirror of
https://github.com/BerriAI/litellm.git
synced 2025-04-25 10:44:24 +00:00
(UI) - Security Improvement, move to JWT Auth for Admin UI Sessions (#8995)
* (UI) - Improvements to session handling logic (#8970)
* add cookieUtils
* use utils for clearing cookies
* on logout use clearTokenCookies
* ui use correct clearTokenCookies
* navbar show userEmail on UserID page
* add timestamp on token cookie
* update generate_authenticated_redirect_response
* use common getAuthToken
* fix clearTokenCookies
* fixes for get auth token
* fix invitation link sign in logic
* Revert "fix invitation link sign in logic"
This reverts commit 30e5308cb3
.
* fix getAuthToken
* update setAuthToken
* fix ui session handling
* fix ui session handler
* bug fix stop generating LiteLLM Virtual keys for access
* working JWT insert into cookies
* use central place to build UI JWT token
* add _validate_ui_token
* fix ui session handler
* fix fetchWithCredentials
* check allowed routes for ui session tokens
* expose validate_session endpoint
* validate session endpoint
* call sso/session/validate
* getUISessionDetails
* ui move to getUISessionDetails
* /sso/session/validate
* fix cookie utils
* use getUISessionDetails
* use ui_session_id
* "/spend/logs/ui" in spend_tracking_routes
* working sign in JWT flow for proxy admin
* allow proxy admin to access ui routes
* use check_route_access
* update types
* update login method
* fixes to ui session handler
* working flow for admin and internal users
* fixes for invite links
* use JWTs for SSO sign in
* fix /invitation/new flow
* fix code quality checks
* fix _get_ui_session_token_from_cookies
* /organization/list
* ui sso sign in
* TestUISessionHandler
* TestUISessionHandler
This commit is contained in:
parent
42931638df
commit
01a44a4e47
17 changed files with 1104 additions and 538 deletions
|
@ -7375,6 +7375,8 @@ async def login(request: Request): # noqa: PLR0915
|
|||
import multipart
|
||||
except ImportError:
|
||||
subprocess.run(["pip", "install", "python-multipart"])
|
||||
from litellm.proxy.management_helpers.ui_session_handler import UISessionHandler
|
||||
|
||||
global master_key
|
||||
if master_key is None:
|
||||
raise ProxyException(
|
||||
|
@ -7445,56 +7447,23 @@ async def login(request: Request): # noqa: PLR0915
|
|||
user_role=user_role,
|
||||
)
|
||||
)
|
||||
if os.getenv("DATABASE_URL") is not None:
|
||||
response = await generate_key_helper_fn(
|
||||
request_type="key",
|
||||
**{
|
||||
"user_role": LitellmUserRoles.PROXY_ADMIN,
|
||||
"duration": "24hr",
|
||||
"key_max_budget": litellm.max_ui_session_budget,
|
||||
"models": [],
|
||||
"aliases": {},
|
||||
"config": {},
|
||||
"spend": 0,
|
||||
"user_id": key_user_id,
|
||||
"team_id": "litellm-dashboard",
|
||||
}, # type: ignore
|
||||
)
|
||||
else:
|
||||
raise ProxyException(
|
||||
message="No Database connected. Set DATABASE_URL in .env. If set, use `--detailed_debug` to debug issue.",
|
||||
type=ProxyErrorTypes.auth_error,
|
||||
param="DATABASE_URL",
|
||||
code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
key = response["token"] # type: ignore
|
||||
litellm_dashboard_ui = os.getenv("PROXY_BASE_URL", "")
|
||||
if litellm_dashboard_ui.endswith("/"):
|
||||
litellm_dashboard_ui += "ui/"
|
||||
else:
|
||||
litellm_dashboard_ui += "/ui/"
|
||||
import jwt
|
||||
|
||||
jwt_token = jwt.encode( # type: ignore
|
||||
{
|
||||
"user_id": user_id,
|
||||
"key": key,
|
||||
"user_email": None,
|
||||
"user_role": user_role, # this is the path without sso - we can assume only admins will use this
|
||||
"login_method": "username_password",
|
||||
"premium_user": premium_user,
|
||||
"auth_header_name": general_settings.get(
|
||||
"litellm_key_header_name", "Authorization"
|
||||
),
|
||||
"disabled_non_admin_personal_key_creation": disabled_non_admin_personal_key_creation,
|
||||
},
|
||||
master_key,
|
||||
algorithm="HS256",
|
||||
jwt_token = UISessionHandler.build_authenticated_ui_jwt_token(
|
||||
user_id=user_id,
|
||||
user_role=user_role,
|
||||
user_email=None,
|
||||
premium_user=premium_user,
|
||||
disabled_non_admin_personal_key_creation=disabled_non_admin_personal_key_creation,
|
||||
login_method="username_password",
|
||||
)
|
||||
litellm_dashboard_ui += "?userID=" + user_id
|
||||
redirect_response = RedirectResponse(url=litellm_dashboard_ui, status_code=303)
|
||||
redirect_response.set_cookie(key="token", value=jwt_token)
|
||||
return redirect_response
|
||||
return UISessionHandler.generate_authenticated_redirect_response(
|
||||
redirect_url=litellm_dashboard_ui, jwt_token=jwt_token
|
||||
)
|
||||
elif _user_row is not None:
|
||||
"""
|
||||
When sharing invite links
|
||||
|
@ -7513,58 +7482,23 @@ async def login(request: Request): # noqa: PLR0915
|
|||
if secrets.compare_digest(password, _password) or secrets.compare_digest(
|
||||
hash_password, _password
|
||||
):
|
||||
if os.getenv("DATABASE_URL") is not None:
|
||||
response = await generate_key_helper_fn(
|
||||
request_type="key",
|
||||
**{ # type: ignore
|
||||
"user_role": user_role,
|
||||
"duration": "24hr",
|
||||
"key_max_budget": litellm.max_ui_session_budget,
|
||||
"models": [],
|
||||
"aliases": {},
|
||||
"config": {},
|
||||
"spend": 0,
|
||||
"user_id": user_id,
|
||||
"team_id": "litellm-dashboard",
|
||||
},
|
||||
)
|
||||
else:
|
||||
raise ProxyException(
|
||||
message="No Database connected. Set DATABASE_URL in .env. If set, use `--detailed_debug` to debug issue.",
|
||||
type=ProxyErrorTypes.auth_error,
|
||||
param="DATABASE_URL",
|
||||
code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
key = response["token"] # type: ignore
|
||||
litellm_dashboard_ui = os.getenv("PROXY_BASE_URL", "")
|
||||
if litellm_dashboard_ui.endswith("/"):
|
||||
litellm_dashboard_ui += "ui/"
|
||||
else:
|
||||
litellm_dashboard_ui += "/ui/"
|
||||
import jwt
|
||||
|
||||
jwt_token = jwt.encode( # type: ignore
|
||||
{
|
||||
"user_id": user_id,
|
||||
"key": key,
|
||||
"user_email": user_email,
|
||||
"user_role": user_role,
|
||||
"login_method": "username_password",
|
||||
"premium_user": premium_user,
|
||||
"auth_header_name": general_settings.get(
|
||||
"litellm_key_header_name", "Authorization"
|
||||
),
|
||||
"disabled_non_admin_personal_key_creation": disabled_non_admin_personal_key_creation,
|
||||
},
|
||||
master_key,
|
||||
algorithm="HS256",
|
||||
jwt_token = UISessionHandler.build_authenticated_ui_jwt_token(
|
||||
user_id=user_id,
|
||||
user_role=user_role,
|
||||
user_email=user_email,
|
||||
premium_user=premium_user,
|
||||
disabled_non_admin_personal_key_creation=disabled_non_admin_personal_key_creation,
|
||||
login_method="username_password",
|
||||
)
|
||||
litellm_dashboard_ui += "?userID=" + user_id
|
||||
redirect_response = RedirectResponse(
|
||||
url=litellm_dashboard_ui, status_code=303
|
||||
return UISessionHandler.generate_authenticated_redirect_response(
|
||||
redirect_url=litellm_dashboard_ui, jwt_token=jwt_token
|
||||
)
|
||||
redirect_response.set_cookie(key="token", value=jwt_token)
|
||||
return redirect_response
|
||||
else:
|
||||
raise ProxyException(
|
||||
message=f"Invalid credentials used to access UI.\nNot valid credentials for {username}",
|
||||
|
@ -7590,6 +7524,8 @@ async def onboarding(invite_link: str):
|
|||
- Get user from db
|
||||
- Pass in user_email if set
|
||||
"""
|
||||
from litellm.proxy.management_helpers.ui_session_handler import UISessionHandler
|
||||
|
||||
global prisma_client, master_key, general_settings
|
||||
if master_key is None:
|
||||
raise ProxyException(
|
||||
|
@ -7646,51 +7582,26 @@ async def onboarding(invite_link: str):
|
|||
|
||||
user_email = user_obj.user_email
|
||||
|
||||
response = await generate_key_helper_fn(
|
||||
request_type="key",
|
||||
**{
|
||||
"user_role": user_obj.user_role,
|
||||
"duration": "24hr",
|
||||
"key_max_budget": litellm.max_ui_session_budget,
|
||||
"models": [],
|
||||
"aliases": {},
|
||||
"config": {},
|
||||
"spend": 0,
|
||||
"user_id": user_obj.user_id,
|
||||
"team_id": "litellm-dashboard",
|
||||
}, # type: ignore
|
||||
)
|
||||
key = response["token"] # type: ignore
|
||||
|
||||
litellm_dashboard_ui = os.getenv("PROXY_BASE_URL", "")
|
||||
if litellm_dashboard_ui.endswith("/"):
|
||||
litellm_dashboard_ui += "ui/onboarding"
|
||||
else:
|
||||
litellm_dashboard_ui += "/ui/onboarding"
|
||||
import jwt
|
||||
|
||||
disabled_non_admin_personal_key_creation = (
|
||||
get_disabled_non_admin_personal_key_creation()
|
||||
)
|
||||
|
||||
jwt_token = jwt.encode( # type: ignore
|
||||
{
|
||||
"user_id": user_obj.user_id,
|
||||
"key": key,
|
||||
"user_email": user_obj.user_email,
|
||||
"user_role": user_obj.user_role,
|
||||
"login_method": "username_password",
|
||||
"premium_user": premium_user,
|
||||
"auth_header_name": general_settings.get(
|
||||
"litellm_key_header_name", "Authorization"
|
||||
),
|
||||
"disabled_non_admin_personal_key_creation": disabled_non_admin_personal_key_creation,
|
||||
},
|
||||
master_key,
|
||||
algorithm="HS256",
|
||||
jwt_token = UISessionHandler.build_authenticated_ui_jwt_token(
|
||||
user_id=user_obj.user_id,
|
||||
user_role=user_obj.user_role,
|
||||
user_email=user_obj.user_email,
|
||||
premium_user=user_obj.premium_user,
|
||||
disabled_non_admin_personal_key_creation=disabled_non_admin_personal_key_creation,
|
||||
login_method="username_password",
|
||||
)
|
||||
|
||||
litellm_dashboard_ui += "?token={}&user_email={}".format(jwt_token, user_email)
|
||||
|
||||
return {
|
||||
"login_url": litellm_dashboard_ui,
|
||||
"token": jwt_token,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue