fix: address SSO/JWT bugfix findings

- ui_sso: thread env_prefix through get_redirect_response_from_openid
  and _get_user_email_and_id_from_result so OKTA_USER_ROLE_ATTRIBUTE
  is honored on the Okta callback path (falls back to
  GENERIC_USER_ROLE_ATTRIBUTE then 'role').
- handle_jwt: use 'is None' rather than 'or' when falling back from
  litellm_jwtauth.audience/issuer to JWT_AUDIENCE/JWT_ISSUER env vars,
  so explicit empty values are not silently overridden.
- proxy_setting_endpoints: use _decrypt_db_variables instead of
  _decrypt_and_set_db_env_variables when restoring redacted SSO
  secrets, to avoid polluting os.environ with lowercase model-field
  names.

Co-authored-by: Yassin Kortam <yassin@berri.ai>
This commit is contained in:
Cursor Agent 2026-05-15 12:05:00 +00:00
parent bffcc0ee0b
commit b7eb716f2f
No known key found for this signature in database
3 changed files with 20 additions and 10 deletions

View file

@ -762,12 +762,12 @@ class JWTHandler:
When both are unset PyJWT only checks the signature and expiry, which
is preserved for backward compatibility but logged once as a warning.
"""
audience = (
litellm_jwtauth.audience if litellm_jwtauth is not None else None
) or os.getenv("JWT_AUDIENCE")
issuer = (
litellm_jwtauth.issuer if litellm_jwtauth is not None else None
) or os.getenv("JWT_ISSUER")
audience = litellm_jwtauth.audience if litellm_jwtauth is not None else None
if audience is None:
audience = os.getenv("JWT_AUDIENCE")
issuer = litellm_jwtauth.issuer if litellm_jwtauth is not None else None
if issuer is None:
issuer = os.getenv("JWT_ISSUER")
if (
audience is None

View file

@ -1796,6 +1796,11 @@ async def auth_callback(request: Request, state: Optional[str] = None): # noqa:
access_token_payload=access_token_payload,
jwt_handler=jwt_handler,
return_to=cp_return_to,
env_prefix=_get_oidc_env_prefix(
_OIDC_PROVIDER_OKTA
if okta_client_id is not None and generic_client_id is None
else _OIDC_PROVIDER_GENERIC
),
)
@ -2830,6 +2835,7 @@ class SSOAuthenticationHandler:
def _get_user_email_and_id_from_result(
result: Optional[Union[OpenID, dict]],
generic_client_id: Optional[str] = None,
env_prefix: str = "GENERIC",
) -> ParsedOpenIDResult:
"""
Gets the user email and id from the OpenID result after validating the email domain
@ -2870,7 +2876,8 @@ class SSOAuthenticationHandler:
# generic client id - override with custom attribute name if specified
if generic_client_id is not None and result is not None:
generic_user_role_attribute_name = os.getenv(
"GENERIC_USER_ROLE_ATTRIBUTE", "role"
f"{env_prefix}_USER_ROLE_ATTRIBUTE",
os.getenv("GENERIC_USER_ROLE_ATTRIBUTE", "role"),
)
user_id = getattr(result, "id", None)
user_email = normalize_email(getattr(result, "email", None))
@ -2908,6 +2915,7 @@ class SSOAuthenticationHandler:
access_token_payload: Optional[dict] = None,
jwt_handler: Optional[JWTHandler] = None,
return_to: Optional[str] = None,
env_prefix: str = "GENERIC",
) -> RedirectResponse:
import jwt
@ -2931,7 +2939,9 @@ class SSOAuthenticationHandler:
# User is Authe'd in - generate key for the UI to access Proxy
parsed_openid_result = (
SSOAuthenticationHandler._get_user_email_and_id_from_result(
result=result, generic_client_id=generic_client_id
result=result,
generic_client_id=generic_client_id,
env_prefix=env_prefix,
)
)
user_email = parsed_openid_result.get("user_email")

View file

@ -854,8 +854,8 @@ async def update_sso_settings(sso_config: SSOConfig):
)
existing_plain: dict = {}
if existing_sso_record and existing_sso_record.sso_settings:
existing_plain = proxy_config._decrypt_and_set_db_env_variables(
environment_variables=dict(existing_sso_record.sso_settings)
existing_plain = proxy_config._decrypt_db_variables(
variables_dict=dict(existing_sso_record.sso_settings)
)
# Update environment variables in config and in memory