From 00c6e33a15b02521252334ca8030aedc82f8abd7 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Mon, 27 Apr 2026 18:47:02 -0700 Subject: [PATCH 1/3] fix(projects): fire useProjects hook for all authenticated users, not just admins --- .../src/app/(dashboard)/hooks/projects/useProjects.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useProjects.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useProjects.ts index 85c8b25645c..221c8e51885 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useProjects.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useProjects.ts @@ -6,7 +6,6 @@ import { deriveErrorMessage, handleError, } from "@/components/networking"; -import { all_admin_roles } from "@/utils/roles"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; // ── Types ──────────────────────────────────────────────────────────────────── @@ -76,12 +75,11 @@ const fetchProjects = async ( // ── Hook ───────────────────────────────────────────────────────────────────── export const useProjects = () => { - const { accessToken, userRole } = useAuthorized(); + const { accessToken } = useAuthorized(); return useQuery({ queryKey: projectKeys.list({}), queryFn: async () => fetchProjects(accessToken!), - enabled: - Boolean(accessToken) && all_admin_roles.includes(userRole || ""), + enabled: Boolean(accessToken), }); }; From c9c935dbd60fc77915191e09d393a16c303c03fa Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Mon, 27 Apr 2026 18:47:09 -0700 Subject: [PATCH 2/3] fix(routes): add /project/list and /project/info to internal_user_routes allowlist --- litellm/proxy/_types.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 92c920ca594..1a204c26238 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -665,6 +665,8 @@ class LiteLLMRoutes(enum.Enum): "/models/{model_id}", "/guardrails/list", "/v2/guardrails/list", + "/project/list", + "/project/info", ] + spend_tracking_routes + key_management_routes From 7c1ffaf0994bf2121c5ea845616e476cf5bafa27 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Mon, 27 Apr 2026 18:47:13 -0700 Subject: [PATCH 3/3] fix(projects): use members_with_roles + LiteLLM_UserTable.teams for membership checks --- .../management_endpoints/project_endpoints.py | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/enterprise/litellm_enterprise/proxy/management_endpoints/project_endpoints.py b/enterprise/litellm_enterprise/proxy/management_endpoints/project_endpoints.py index f6ed7767c46..4bfe9d31874 100644 --- a/enterprise/litellm_enterprise/proxy/management_endpoints/project_endpoints.py +++ b/enterprise/litellm_enterprise/proxy/management_endpoints/project_endpoints.py @@ -857,10 +857,16 @@ async def project_info( where={"team_id": project.team_id} ) if team: - is_team_member = ( - user_api_key_dict.user_id in team.admins - or user_api_key_dict.user_id in team.members - ) + caller_user_id = user_api_key_dict.user_id + for m in team.members_with_roles or []: + m_user_id = ( + m.get("user_id") + if isinstance(m, dict) + else getattr(m, "user_id", None) + ) + if m_user_id == caller_user_id: + is_team_member = True + break if not (is_admin or is_team_member): raise HTTPException( @@ -911,20 +917,20 @@ async def list_projects( include={"litellm_budget_table": True, "object_permission": True} ) else: - # Get projects for teams the user belongs to - user_teams = await prisma_client.db.litellm_teamtable.find_many( - where={ - "OR": [ - {"members": {"has": user_api_key_dict.user_id}}, - {"admins": {"has": user_api_key_dict.user_id}}, - ] - } + # Look up the user's team memberships via the reverse-index on + # LiteLLM_UserTable.teams (maintained by team_member_add alongside + # members_with_roles). This avoids a full scan of all team rows. + user_record = await prisma_client.db.litellm_usertable.find_unique( + where={"user_id": user_api_key_dict.user_id}, + ) + user_team_ids = ( + user_record.teams + if user_record is not None and user_record.teams + else [] ) - team_ids = [team.team_id for team in user_teams] - projects = await prisma_client.db.litellm_projecttable.find_many( - where={"team_id": {"in": team_ids}}, + where={"team_id": {"in": user_team_ids}}, include={"litellm_budget_table": True, "object_permission": True}, )