arize-phoenix 12.2.0__py3-none-any.whl → 12.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of arize-phoenix might be problematic. Click here for more details.

Files changed (23) hide show
  1. {arize_phoenix-12.2.0.dist-info → arize_phoenix-12.3.0.dist-info}/METADATA +1 -1
  2. {arize_phoenix-12.2.0.dist-info → arize_phoenix-12.3.0.dist-info}/RECORD +22 -21
  3. phoenix/server/api/auth_messages.py +46 -0
  4. phoenix/server/api/routers/oauth2.py +48 -28
  5. phoenix/server/app.py +5 -0
  6. phoenix/server/cost_tracking/model_cost_manifest.json +54 -54
  7. phoenix/server/oauth2.py +2 -4
  8. phoenix/server/static/.vite/manifest.json +39 -39
  9. phoenix/server/static/assets/{components-BG6v0EM8.js → components-Bs8eJEpU.js} +422 -377
  10. phoenix/server/static/assets/{index-CSVcULw1.js → index-C6WEu5UP.js} +12 -12
  11. phoenix/server/static/assets/{pages-DgaM7kpM.js → pages-D-n2pkoG.js} +542 -488
  12. phoenix/server/static/assets/vendor-D2eEI-6h.js +914 -0
  13. phoenix/server/static/assets/{vendor-arizeai-DlOj0PQQ.js → vendor-arizeai-kfOei7nf.js} +2 -2
  14. phoenix/server/static/assets/{vendor-codemirror-B2PHH5yZ.js → vendor-codemirror-1bq_t1Ec.js} +3 -3
  15. phoenix/server/static/assets/{vendor-recharts-CKsi4IjN.js → vendor-recharts-DQ4xfrf4.js} +1 -1
  16. phoenix/server/static/assets/{vendor-shiki-DN26BkKE.js → vendor-shiki-GGmcIQxA.js} +1 -1
  17. phoenix/server/templates/index.html +1 -0
  18. phoenix/version.py +1 -1
  19. phoenix/server/static/assets/vendor-BqTEkGQU.js +0 -903
  20. {arize_phoenix-12.2.0.dist-info → arize_phoenix-12.3.0.dist-info}/WHEEL +0 -0
  21. {arize_phoenix-12.2.0.dist-info → arize_phoenix-12.3.0.dist-info}/entry_points.txt +0 -0
  22. {arize_phoenix-12.2.0.dist-info → arize_phoenix-12.3.0.dist-info}/licenses/IP_NOTICE +0 -0
  23. {arize_phoenix-12.2.0.dist-info → arize_phoenix-12.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arize-phoenix
3
- Version: 12.2.0
3
+ Version: 12.3.0
4
4
  Summary: AI Observability and Evaluation
5
5
  Project-URL: Documentation, https://arize.com/docs/phoenix/
6
6
  Project-URL: Issues, https://github.com/Arize-ai/phoenix/issues
@@ -6,7 +6,7 @@ phoenix/exceptions.py,sha256=n2L2KKuecrdflB9MsCdAYCiSEvGJptIsfRkXMoJle7A,169
6
6
  phoenix/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
7
7
  phoenix/services.py,sha256=ngkyKGVatX3cO2WJdo2hKdaVKP-xJCMvqthvga6kJss,5196
8
8
  phoenix/settings.py,sha256=2kHfT3BNOVd4dAO1bq-syEQbHSG8oX2-7NhOwK2QREk,896
9
- phoenix/version.py,sha256=p9gY3XMRcwNNqpNvGH72AMcUYju-jWhtfef9jZuN95Q,23
9
+ phoenix/version.py,sha256=joovLOjrezzyo7EzpCd_GmtqwFzRWnqjgTP-wu-ic5w,23
10
10
  phoenix/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  phoenix/core/embedding_dimension.py,sha256=zKGbcvwOXgLf-yrJBpQyKtd-LEOPRKHnUToyAU8Owis,87
12
12
  phoenix/core/model.py,sha256=qBFraOtmwCCnWJltKNP18DDG0mULXigytlFsa6YOz6k,4837
@@ -102,7 +102,7 @@ phoenix/pointcloud/pointcloud.py,sha256=SN_1wXZcwKrtSnHGZLDZGx71orqE1WyVF7E-D58d
102
102
  phoenix/pointcloud/projectors.py,sha256=TQgwc9cJDjJkin1WZyZzgl3HsYrLLiyWD7Czy4jNW3U,1088
103
103
  phoenix/pointcloud/umap_parameters.py,sha256=db_WEPoamuWtopZx7tQfAXPnoE0MS8FkAV0_ThjEx_Q,1735
104
104
  phoenix/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
105
- phoenix/server/app.py,sha256=bN92VbiRRaay3cwBn6U60CCQFkz5JycBD6mrV8qjPp8,50490
105
+ phoenix/server/app.py,sha256=Nkm7xwzed5H_ArLWiqn4Id5ERxB-k-1sJSsvvtWw4Pk,50868
106
106
  phoenix/server/authorization.py,sha256=OxROn7ibpKtCTrcgDkzWuNxVaQcSQ8MAx7zbjZiliK0,3201
107
107
  phoenix/server/bearer_auth.py,sha256=f4v4W94KyTdGGCPsK1tXOe0vouPuvanAEa03XSdCvPE,6650
108
108
  phoenix/server/dml_event.py,sha256=HgUsIkzyXxCGasjnrqubegWbNaAcAgjrtWrKc3aRwxA,3245
@@ -110,7 +110,7 @@ phoenix/server/dml_event_handler.py,sha256=71_iPcHiJ4E-8Z7sGL2j0vx_RpkmcMVEUFIBs
110
110
  phoenix/server/grpc_server.py,sha256=THDdkMKXQEC3gZjFG2nUgXHFbUnWsRHgFM9oh5WtAX0,4758
111
111
  phoenix/server/jwt_store.py,sha256=B6uVildN_dQDTG_-aHHvuVSI7wIVK1yvED-_y6se2GU,16905
112
112
  phoenix/server/main.py,sha256=UBwxrQIEE7ci-SbE6GAlRYmbMHooI6JYG6sG-UpBFFs,18905
113
- phoenix/server/oauth2.py,sha256=GvUqZBoZ5dG-l2G1RMl1SUcN10jNAjaMXFznMSWz2Zs,3336
113
+ phoenix/server/oauth2.py,sha256=l6jugh-lHq0h7h7TBzvk6oTrHKXe1aX9-j7UAat5Eac,3224
114
114
  phoenix/server/prometheus.py,sha256=ovh6Hrw2mLUC6KddGydT0_lv-yK0upV9mL-T9UjqlcE,9373
115
115
  phoenix/server/rate_limiters.py,sha256=cFc73D2NaxqNZZDbwfIDw4So-fRVOJPBtqxOZ8Qky_s,7155
116
116
  phoenix/server/retention.py,sha256=MQe1FWuc_NxhqgIq5q2hfFhWT8ddAmpppgI74xYEQ6c,3064
@@ -122,6 +122,7 @@ phoenix/server/utils.py,sha256=zTgY07W9GOpAFvIIPVBfXAGQb6kwGla2lhj1VGTCeIU,3057
122
122
  phoenix/server/api/README.md,sha256=Pyq1PLPgTzXAswrfIhGXrjI3Skq8it2jTVnanT6Ba4Q,1162
123
123
  phoenix/server/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
124
124
  phoenix/server/api/auth.py,sha256=AyYhnZIbY9ALVjg2K6aC2UXSa3Pva5GVDBXyaZ3nD3o,2749
125
+ phoenix/server/api/auth_messages.py,sha256=15oqogBNS2BgJLLDecfXz62G9LdiOAEyzPv6jX9BxYE,1912
125
126
  phoenix/server/api/context.py,sha256=rdjGb0Ce-XBFVF5lj-lA8VoyKmrrPpLdU_BKT0qhFyQ,10450
126
127
  phoenix/server/api/exceptions.py,sha256=9gB4nBRNX6k4_fsQZ12yxw6Tw53h_915l06DYK-qkPQ,1442
127
128
  phoenix/server/api/interceptor.py,sha256=ykDnoC_apUd-llVli3m1CW18kNSIgjz2qZ6m5JmPDu8,1294
@@ -280,7 +281,7 @@ phoenix/server/api/openapi/schema.py,sha256=WGmHWSIyJhtc5EIh_M3vlXU-EgHkFuTlyVof
280
281
  phoenix/server/api/routers/__init__.py,sha256=YIzHsIFOOXuCRbDkMUHx-McrANFJK5UfUn6a4BNIzmo,277
281
282
  phoenix/server/api/routers/auth.py,sha256=hPkMmLgy9oV-V8WSlwemIbb3jiwXzzLZDW10GAfkY-o,11928
282
283
  phoenix/server/api/routers/embeddings.py,sha256=BpZGJee0pdL0W5Rp1L0b30dEtZTgJeVqXky8LgZ0ZXw,898
283
- phoenix/server/api/routers/oauth2.py,sha256=HFBYyPlUZXsik36uyzcIOFfVZHNda8X5BuB36TU7Tyw,23989
284
+ phoenix/server/api/routers/oauth2.py,sha256=AwfWqTQyHpXdJ3BUTkIvsgUiALAlFbERPs1pyJOM3Lo,25313
284
285
  phoenix/server/api/routers/utils.py,sha256=M41BoH-fl37izhRuN2aX7lWm7jOC20A_3uClv9TVUUY,583
285
286
  phoenix/server/api/routers/v1/__init__.py,sha256=Ga6SjcX6X6Aw5VTvbiPOIXfzQS8lbVERY0BHdx5Dlks,2881
286
287
  phoenix/server/api/routers/v1/annotation_configs.py,sha256=xp5lJmKYlRsINCUrRD9-lTAElw2v4hdFndS5BWrxICA,16048
@@ -398,7 +399,7 @@ phoenix/server/cost_tracking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
398
399
  phoenix/server/cost_tracking/cost_details_calculator.py,sha256=Tt0YcuLhgPuXKWJemWVmYQfG0xQUvH4VziIj6KcDnoA,8945
399
400
  phoenix/server/cost_tracking/cost_model_lookup.py,sha256=jhtVdnQBzrTUHeOGPWgOebk-Io5hpJ1vAgWOu8ojeJ4,6801
400
401
  phoenix/server/cost_tracking/helpers.py,sha256=Pk6ECjnYreTxrldtRwxnwFcxIPVsvDq_yAwDA_spkOc,2122
401
- phoenix/server/cost_tracking/model_cost_manifest.json,sha256=rS_jFEJijHH93LDnh3Zadmfwzr7GJAlcjvWZ0xR4He4,68561
402
+ phoenix/server/cost_tracking/model_cost_manifest.json,sha256=qPDorKVjL9O9OtBTcHUW38yq6WfGSw2lYjnDTAlbb6s,68581
402
403
  phoenix/server/cost_tracking/regex_specificity.py,sha256=9kqWuQ68C-hlwW25hr7BhFlRt5y2Nnpy0Ax3n9UN6Xk,11622
403
404
  phoenix/server/cost_tracking/token_cost_calculator.py,sha256=2JEZnvusx2-xbhp8krp9EarjWuyGH2KO4e-ZwJX-K0s,1598
404
405
  phoenix/server/daemons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -427,19 +428,19 @@ phoenix/server/static/apple-touch-icon-76x76.png,sha256=CT_xT12I0u2i0WU8JzBZBuOQ
427
428
  phoenix/server/static/apple-touch-icon.png,sha256=fOfpjqGpWYbJ0eAurKsyoZP1EAs6ZVooBJ_SGk2ZkDs,3801
428
429
  phoenix/server/static/favicon.ico,sha256=bY0vvCKRftemZfPShwZtE93DiiQdaYaozkPGwNFr6H8,34494
429
430
  phoenix/server/static/modernizr.js,sha256=mvK-XtkNqjOral-QvzoqsyOMECXIMu5BQwSVN_wcU9c,2564
430
- phoenix/server/static/.vite/manifest.json,sha256=rzd1dpIB-2jzG1MUY69jIZxY7fHIDnSVfIl3SHJrmTw,2328
431
- phoenix/server/static/assets/components-BG6v0EM8.js,sha256=OlwDlPZR35cYxPcpZu7nTvuX-XJ5bMAKb91GBT7rbF8,717341
432
- phoenix/server/static/assets/index-CSVcULw1.js,sha256=MxKVdXMdZV8wxvcl47H-BUzsRCuihATaJefd_uxKa88,63600
433
- phoenix/server/static/assets/pages-DgaM7kpM.js,sha256=b0KVlxRkpFTZ9S7DCZiE06Qruc7Ua2CTKLxhfiyHGDM,1331042
431
+ phoenix/server/static/.vite/manifest.json,sha256=Pu8-OxoLf8RlAl-i0K8pAuKfVy6h7ZuOBJR9psD5frE,2328
432
+ phoenix/server/static/assets/components-Bs8eJEpU.js,sha256=ml92PuSdVdsbHS8q6c4sHSWJ9nBSjo1l1qvjzM6EVF4,723590
433
+ phoenix/server/static/assets/index-C6WEu5UP.js,sha256=mY6KSmhD7Okipz8iEqbzZpyFQG_fVUIpNfKiT8ND8OM,63680
434
+ phoenix/server/static/assets/pages-D-n2pkoG.js,sha256=rtkn8DSkEKuvyFlbSF6dUn_uGCVyUBZZh--yByZSEmI,1341008
434
435
  phoenix/server/static/assets/vendor-BGzfc4EU.css,sha256=Nx5Lmx-bqYR7nsO_O4kEBcrJ8cwknWjZ6seHN3_s4UQ,3171
435
- phoenix/server/static/assets/vendor-BqTEkGQU.js,sha256=_reetQ4kYw-0cLbQgK-Ce_bu8P-udNgkFAxAMWAOuIQ,2602283
436
- phoenix/server/static/assets/vendor-arizeai-DlOj0PQQ.js,sha256=fvk6Zfq3daaYLB4RiZRJHpJm5GIyzqNV4JXt74ZWeCg,107800
437
- phoenix/server/static/assets/vendor-codemirror-B2PHH5yZ.js,sha256=QjUU6KX46yAL8tjl7SsEsj1jTY6M33GwtK8S4eaE4uk,413211
438
- phoenix/server/static/assets/vendor-recharts-CKsi4IjN.js,sha256=d07g7qsq4yG80ekO_0m5RQ20ZMVEfnJvXV8boHnGHZc,231652
439
- phoenix/server/static/assets/vendor-shiki-DN26BkKE.js,sha256=gCX22-6kpVbLa_zs_Mapak8jPzvBcXMQ5WpCiIv2T5s,305160
436
+ phoenix/server/static/assets/vendor-D2eEI-6h.js,sha256=DcZ_ov-x3PP13qfDh2X0hhQTuuSnxoMnYVBW98YBo0Q,2709850
437
+ phoenix/server/static/assets/vendor-arizeai-kfOei7nf.js,sha256=AlUQvzFvgUY2d5qzujvi4N7VXbhmpcnrKAYk9Ilpzt4,107788
438
+ phoenix/server/static/assets/vendor-codemirror-1bq_t1Ec.js,sha256=cjHHniUz2eOaXgg3iPcyd3d8hRz_2z7tW_UTPiMyGiA,413211
439
+ phoenix/server/static/assets/vendor-recharts-DQ4xfrf4.js,sha256=tTNG6AAxOGuPbEnjQkr3EzR7COVhDnN37QJFV6BNQg8,231652
440
+ phoenix/server/static/assets/vendor-shiki-GGmcIQxA.js,sha256=U357g2RSPGwucCoww2yscGQcLyWIcP3EQxlkMgR9CJg,305160
440
441
  phoenix/server/static/assets/vendor-three-BLWp5bic.js,sha256=vfSCVXS20jA0Ceo_O0mDxYBcROinWMdPE6RR4JXmtec,620972
441
442
  phoenix/server/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
442
- phoenix/server/templates/index.html,sha256=QAYh0TG5mg-GvDQUR09aD9ebl9Sfq0fYAcfIa5G7J6E,7148
443
+ phoenix/server/templates/index.html,sha256=_iKIyXEDDr5cTTnrUCjCd617U6Alc1k-IXtdKSt8g14,7215
443
444
  phoenix/session/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
444
445
  phoenix/session/client.py,sha256=bFw3RzSdCCQBDNDbgxgaao4kCsP9_YWXG1VQfJvNveA,38532
445
446
  phoenix/session/data_extractor.py,sha256=y_21QYW5emZ-SFYN9GtpUwDBMPCWtbRk3ZGxfR3k8K0,3178
@@ -476,9 +477,9 @@ phoenix/utilities/project.py,sha256=auVpARXkDb-JgeX5f2aStyFIkeKvGwN9l7qrFeJMVxI,
476
477
  phoenix/utilities/re.py,sha256=6YyUWIkv0zc2SigsxfOWIHzdpjKA_TZo2iqKq7zJKvw,2081
477
478
  phoenix/utilities/span_store.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
478
479
  phoenix/utilities/template_formatters.py,sha256=gh9PJD6WEGw7TEYXfSst1UR4pWWwmjxMLrDVQ_CkpkQ,2779
479
- arize_phoenix-12.2.0.dist-info/METADATA,sha256=WjI2-I68gw_gsPc4SQ945twJ3uGSp0t66IFzKXRJOsQ,34045
480
- arize_phoenix-12.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
481
- arize_phoenix-12.2.0.dist-info/entry_points.txt,sha256=Pgpn8Upxx9P8z8joPXZWl2LlnAlGc3gcQoVchb06X1Q,94
482
- arize_phoenix-12.2.0.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
483
- arize_phoenix-12.2.0.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
484
- arize_phoenix-12.2.0.dist-info/RECORD,,
480
+ arize_phoenix-12.3.0.dist-info/METADATA,sha256=NBdFfBR6OscGNJ7RHEM0IhHe2mnvNAie9rm_UlWvHmA,34045
481
+ arize_phoenix-12.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
482
+ arize_phoenix-12.3.0.dist-info/entry_points.txt,sha256=Pgpn8Upxx9P8z8joPXZWl2LlnAlGc3gcQoVchb06X1Q,94
483
+ arize_phoenix-12.3.0.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
484
+ arize_phoenix-12.3.0.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
485
+ arize_phoenix-12.3.0.dist-info/RECORD,,
@@ -0,0 +1,46 @@
1
+ # ruff: noqa: E501
2
+ """
3
+ Authentication error and success message codes.
4
+
5
+ These codes are used in authentication flows to safely communicate status
6
+ to users via query parameters. Using codes instead of raw messages prevents
7
+ social engineering and phishing attacks.
8
+
9
+ The messages are passed to the frontend via window.Config to ensure a single
10
+ source of truth between backend and frontend.
11
+ """
12
+
13
+ from types import MappingProxyType
14
+ from typing import Literal, Mapping, get_args
15
+
16
+ # Error code type - used for type hints in redirect functions
17
+ AuthErrorCode = Literal[
18
+ "unknown_idp",
19
+ "auth_failed",
20
+ "invalid_state",
21
+ "unsafe_return_url",
22
+ "oauth_error",
23
+ "no_oidc_support",
24
+ "missing_email_scope",
25
+ "email_in_use",
26
+ "sign_in_not_allowed",
27
+ ]
28
+
29
+ # Error messages - passed to frontend via window.Config.authErrorMessages
30
+ # Backend generates these codes when redirecting users after OAuth errors
31
+ AUTH_ERROR_MESSAGES: Mapping[AuthErrorCode, str] = MappingProxyType(
32
+ {
33
+ "unknown_idp": "Unknown identity provider.",
34
+ "auth_failed": "Authentication failed. Please contact your administrator.",
35
+ "invalid_state": "Invalid authentication state. Please try again.",
36
+ "unsafe_return_url": "Invalid return URL. Please try again.",
37
+ "oauth_error": "Authentication failed. Please try again.",
38
+ "no_oidc_support": "Your identity provider does not appear to support OpenID Connect. Please contact your administrator.",
39
+ "missing_email_scope": "Please ensure your identity provider is configured to use the 'email' scope.",
40
+ "email_in_use": "An account with this email already exists.",
41
+ "sign_in_not_allowed": "Sign in is not allowed. Please contact your administrator.",
42
+ }
43
+ )
44
+
45
+ # Runtime assertion to ensure AUTH_ERROR_MESSAGES keys match AuthErrorCode Literal values
46
+ assert set(AUTH_ERROR_MESSAGES.keys()) == set(get_args(AuthErrorCode))
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  import re
2
3
  from dataclasses import dataclass
3
4
  from datetime import timedelta
@@ -38,8 +39,8 @@ from phoenix.config import (
38
39
  get_env_disable_rate_limit,
39
40
  )
40
41
  from phoenix.db import models
42
+ from phoenix.server.api.auth_messages import AuthErrorCode
41
43
  from phoenix.server.bearer_auth import create_access_and_refresh_tokens
42
- from phoenix.server.oauth2 import OAuth2Client
43
44
  from phoenix.server.rate_limiters import (
44
45
  ServerRateLimiter,
45
46
  fastapi_ip_rate_limiter,
@@ -50,6 +51,8 @@ from phoenix.server.utils import get_root_path, prepend_root_path
50
51
 
51
52
  _LOWERCASE_ALPHANUMS_AND_UNDERSCORES = r"[a-z0-9_]+"
52
53
 
54
+ logger = logging.getLogger(__name__)
55
+
53
56
  login_rate_limiter = fastapi_ip_rate_limiter(
54
57
  ServerRateLimiter(
55
58
  per_second_rate_limit=0.2,
@@ -88,11 +91,12 @@ async def login(
88
91
  idp_name: Annotated[str, Path(min_length=1, pattern=_LOWERCASE_ALPHANUMS_AND_UNDERSCORES)],
89
92
  return_url: Optional[str] = Query(default=None, alias="returnUrl"),
90
93
  ) -> RedirectResponse:
94
+ # Security Note: Query parameters should be treated as untrusted user input. Never display
95
+ # these values directly to users as they could be manipulated for XSS, phishing, or social
96
+ # engineering attacks.
97
+ if (oauth2_client := request.app.state.oauth2_clients.get_client(idp_name)) is None:
98
+ return _redirect_to_login(request=request, error="unknown_idp")
91
99
  secret = request.app.state.get_secret()
92
- if not isinstance(
93
- oauth2_client := request.app.state.oauth2_clients.get_client(idp_name), OAuth2Client
94
- ):
95
- return _redirect_to_login(request=request, error=f"Unknown IDP: {idp_name}.")
96
100
  if (referer := request.headers.get("referer")) is not None:
97
101
  # if the referer header is present, use it as the origin URL
98
102
  parsed_url = urlparse(referer)
@@ -132,28 +136,42 @@ async def create_tokens(
132
136
  request: Request,
133
137
  idp_name: Annotated[str, Path(min_length=1, pattern=_LOWERCASE_ALPHANUMS_AND_UNDERSCORES)],
134
138
  state: str = Query(),
135
- authorization_code: str = Query(alias="code"),
139
+ authorization_code: Optional[str] = Query(default=None, alias="code"),
140
+ error: Optional[str] = Query(default=None),
141
+ error_description: Optional[str] = Query(default=None),
136
142
  stored_state: str = Cookie(alias=PHOENIX_OAUTH2_STATE_COOKIE_NAME),
137
143
  stored_nonce: str = Cookie(alias=PHOENIX_OAUTH2_NONCE_COOKIE_NAME),
138
144
  ) -> RedirectResponse:
145
+ # Security Note: Query parameters should be treated as untrusted user input. Never display
146
+ # these values directly to users as they could be manipulated for XSS, phishing, or social
147
+ # engineering attacks.
148
+ if (oauth2_client := request.app.state.oauth2_clients.get_client(idp_name)) is None:
149
+ return _redirect_to_login(request=request, error="unknown_idp")
150
+ if error or error_description:
151
+ logger.error(
152
+ "OAuth2 authentication failed for IDP %s: error=%s, description=%s",
153
+ idp_name,
154
+ error,
155
+ error_description,
156
+ )
157
+ return _redirect_to_login(request=request, error="auth_failed")
158
+ if authorization_code is None:
159
+ logger.error("OAuth2 callback missing authorization code for IDP %s", idp_name)
160
+ return _redirect_to_login(request=request, error="auth_failed")
139
161
  secret = request.app.state.get_secret()
140
162
  if state != stored_state:
141
- return _redirect_to_login(request=request, error=_INVALID_OAUTH2_STATE_MESSAGE)
163
+ return _redirect_to_login(request=request, error="invalid_state")
142
164
  try:
143
165
  payload = _parse_state_payload(secret=secret, state=state)
144
166
  except JoseError:
145
- return _redirect_to_login(request=request, error=_INVALID_OAUTH2_STATE_MESSAGE)
167
+ return _redirect_to_login(request=request, error="invalid_state")
146
168
  if (return_url := payload.get("return_url")) is not None and not _is_relative_url(
147
169
  unquote(return_url)
148
170
  ):
149
- return _redirect_to_login(request=request, error="Attempting login with unsafe return URL.")
171
+ return _redirect_to_login(request=request, error="unsafe_return_url")
150
172
  assert isinstance(access_token_expiry := request.app.state.access_token_expiry, timedelta)
151
173
  assert isinstance(refresh_token_expiry := request.app.state.refresh_token_expiry, timedelta)
152
174
  token_store: TokenStore = request.app.state.get_token_store()
153
- if not isinstance(
154
- oauth2_client := request.app.state.oauth2_clients.get_client(idp_name), OAuth2Client
155
- ):
156
- return _redirect_to_login(request=request, error=f"Unknown IDP: {idp_name}.")
157
175
  try:
158
176
  token_data = await oauth2_client.fetch_access_token(
159
177
  state=state,
@@ -162,19 +180,19 @@ async def create_tokens(
162
180
  request=request, origin_url=payload["origin_url"], idp_name=idp_name
163
181
  ),
164
182
  )
165
- except OAuthError as error:
166
- return _redirect_to_login(request=request, error=str(error))
183
+ except OAuthError as e:
184
+ logger.error("OAuth2 error for IDP %s: %s", idp_name, e)
185
+ return _redirect_to_login(request=request, error="oauth_error")
167
186
  _validate_token_data(token_data)
168
187
  if "id_token" not in token_data:
169
- return _redirect_to_login(
170
- request=request,
171
- error=f"OAuth2 IDP {idp_name} does not appear to support OpenID Connect.",
172
- )
188
+ logger.error("OAuth2 IDP %s does not appear to support OpenID Connect", idp_name)
189
+ return _redirect_to_login(request=request, error="no_oidc_support")
173
190
  user_info = await oauth2_client.parse_id_token(token_data, nonce=stored_nonce)
174
191
  try:
175
192
  user_info = _parse_user_info(user_info)
176
- except MissingEmailScope as error:
177
- return _redirect_to_login(request=request, error=str(error))
193
+ except MissingEmailScope as e:
194
+ logger.error("Missing email scope for IDP %s: %s", idp_name, e)
195
+ return _redirect_to_login(request=request, error="missing_email_scope")
178
196
 
179
197
  try:
180
198
  async with request.app.state.db() as session:
@@ -184,8 +202,12 @@ async def create_tokens(
184
202
  user_info=user_info,
185
203
  allow_sign_up=oauth2_client.allow_sign_up,
186
204
  )
187
- except (EmailAlreadyInUse, SignInNotAllowed) as error:
188
- return _redirect_to_login(request=request, error=str(error))
205
+ except EmailAlreadyInUse as e:
206
+ logger.error("Email already in use for IDP %s: %s", idp_name, e)
207
+ return _redirect_to_login(request=request, error="email_in_use")
208
+ except SignInNotAllowed as e:
209
+ logger.error("Sign in not allowed for IDP %s: %s", idp_name, e)
210
+ return _redirect_to_login(request=request, error="sign_in_not_allowed")
189
211
  access_token, refresh_token = await create_access_and_refresh_tokens(
190
212
  user=user,
191
213
  token_store=token_store,
@@ -560,9 +582,10 @@ class MissingEmailScope(Exception):
560
582
  pass
561
583
 
562
584
 
563
- def _redirect_to_login(*, request: Request, error: str) -> RedirectResponse:
585
+ def _redirect_to_login(*, request: Request, error: AuthErrorCode) -> RedirectResponse:
564
586
  """
565
- Creates a RedirectResponse to the login page to display an error message.
587
+ Creates a RedirectResponse to the login page to display an error code.
588
+ The error code will be validated and mapped to a user-friendly message on the frontend.
566
589
  """
567
590
  # TODO: this needs some cleanup
568
591
  login_path = prepend_root_path(
@@ -661,7 +684,4 @@ def _is_oauth2_state_payload(maybe_state_payload: Any) -> TypeGuard[_OAuth2State
661
684
 
662
685
 
663
686
  _JWT_ALGORITHM = "HS256"
664
- _INVALID_OAUTH2_STATE_MESSAGE = (
665
- "Received invalid state parameter during OAuth2 authorization code flow for IDP {idp_name}."
666
- )
667
687
  _RELATIVE_URL_PATTERN = re.compile(r"^/($|\w)")
phoenix/server/app.py CHANGED
@@ -81,6 +81,7 @@ from phoenix.db.facilitator import Facilitator
81
81
  from phoenix.db.helpers import SupportedSQLDialect
82
82
  from phoenix.exceptions import PhoenixMigrationError
83
83
  from phoenix.pointcloud.umap_parameters import UMAPParameters
84
+ from phoenix.server.api.auth_messages import AUTH_ERROR_MESSAGES, AuthErrorCode
84
85
  from phoenix.server.api.context import Context, DataLoaders
85
86
  from phoenix.server.api.dataloaders import (
86
87
  AnnotationConfigsByProjectDataLoader,
@@ -250,6 +251,8 @@ class AppConfig(NamedTuple):
250
251
  web_manifest_path: Path
251
252
  authentication_enabled: bool
252
253
  """ Whether authentication is enabled """
254
+ auth_error_messages: dict[AuthErrorCode, str]
255
+ """ Mapping of auth error codes to user-friendly messages """
253
256
  oauth2_idps: Sequence[OAuth2Idp]
254
257
  basic_auth_disabled: bool = False
255
258
  auto_login_idp_name: Optional[str] = None
@@ -326,6 +329,7 @@ class Static(StaticFiles):
326
329
  "support_email": self._app_config.support_email,
327
330
  "has_db_threshold": self._app_config.has_db_threshold,
328
331
  "allow_external_resources": self._app_config.allow_external_resources,
332
+ "auth_error_messages": self._app_config.auth_error_messages,
329
333
  },
330
334
  )
331
335
  except Exception as e:
@@ -1146,6 +1150,7 @@ def create_app(
1146
1150
  and get_env_database_usage_insertion_blocking_threshold_percentage()
1147
1151
  ),
1148
1152
  allow_external_resources=get_env_allow_external_resources(),
1153
+ auth_error_messages=dict(AUTH_ERROR_MESSAGES) if authentication_enabled else {},
1149
1154
  ),
1150
1155
  ),
1151
1156
  name="static",
@@ -449,6 +449,60 @@
449
449
  }
450
450
  ]
451
451
  },
452
+ {
453
+ "name": "claude-sonnet-4-5",
454
+ "name_pattern": "claude-sonnet-4-5",
455
+ "source": "litellm",
456
+ "token_prices": [
457
+ {
458
+ "base_rate": 3e-6,
459
+ "is_prompt": true,
460
+ "token_type": "input"
461
+ },
462
+ {
463
+ "base_rate": 0.000015,
464
+ "is_prompt": false,
465
+ "token_type": "output"
466
+ },
467
+ {
468
+ "base_rate": 3e-7,
469
+ "is_prompt": true,
470
+ "token_type": "cache_read"
471
+ },
472
+ {
473
+ "base_rate": 3.75e-6,
474
+ "is_prompt": true,
475
+ "token_type": "cache_write"
476
+ }
477
+ ]
478
+ },
479
+ {
480
+ "name": "claude-sonnet-4-5-20250929",
481
+ "name_pattern": "claude-sonnet-4-5-20250929",
482
+ "source": "litellm",
483
+ "token_prices": [
484
+ {
485
+ "base_rate": 3e-6,
486
+ "is_prompt": true,
487
+ "token_type": "input"
488
+ },
489
+ {
490
+ "base_rate": 0.000015,
491
+ "is_prompt": false,
492
+ "token_type": "output"
493
+ },
494
+ {
495
+ "base_rate": 3e-7,
496
+ "is_prompt": true,
497
+ "token_type": "cache_read"
498
+ },
499
+ {
500
+ "base_rate": 3.75e-6,
501
+ "is_prompt": true,
502
+ "token_type": "cache_write"
503
+ }
504
+ ]
505
+ },
452
506
  {
453
507
  "name": "gemini-2.0-flash",
454
508
  "name_pattern": "gemini-2.0-flash(@[a-zA-Z0-9]+)?",
@@ -1028,60 +1082,6 @@
1028
1082
  }
1029
1083
  ]
1030
1084
  },
1031
- {
1032
- "name": "gemini-flash-latest",
1033
- "name_pattern": "gemini-flash-latest",
1034
- "source": "litellm",
1035
- "token_prices": [
1036
- {
1037
- "base_rate": 3e-7,
1038
- "is_prompt": true,
1039
- "token_type": "input"
1040
- },
1041
- {
1042
- "base_rate": 2.5e-6,
1043
- "is_prompt": false,
1044
- "token_type": "output"
1045
- },
1046
- {
1047
- "base_rate": 7.5e-8,
1048
- "is_prompt": true,
1049
- "token_type": "cache_read"
1050
- },
1051
- {
1052
- "base_rate": 1e-6,
1053
- "is_prompt": true,
1054
- "token_type": "audio"
1055
- }
1056
- ]
1057
- },
1058
- {
1059
- "name": "gemini-flash-lite-latest",
1060
- "name_pattern": "gemini-flash-lite-latest",
1061
- "source": "litellm",
1062
- "token_prices": [
1063
- {
1064
- "base_rate": 1e-7,
1065
- "is_prompt": true,
1066
- "token_type": "input"
1067
- },
1068
- {
1069
- "base_rate": 4e-7,
1070
- "is_prompt": false,
1071
- "token_type": "output"
1072
- },
1073
- {
1074
- "base_rate": 2.5e-8,
1075
- "is_prompt": true,
1076
- "token_type": "cache_read"
1077
- },
1078
- {
1079
- "base_rate": 3e-7,
1080
- "is_prompt": true,
1081
- "token_type": "audio"
1082
- }
1083
- ]
1084
- },
1085
1085
  {
1086
1086
  "name": "gpt-3.5-turbo",
1087
1087
  "name_pattern": "gpt-(35|3.5)-turbo",
phoenix/server/oauth2.py CHANGED
@@ -83,10 +83,8 @@ class OAuth2Clients:
83
83
  self._auto_login_client = client
84
84
  self._clients[config.idp_name] = client
85
85
 
86
- def get_client(self, idp_name: str) -> OAuth2Client:
87
- if (client := self._clients.get(idp_name)) is None:
88
- raise ValueError(f"unknown or unregistered OAuth2 client: {idp_name}")
89
- return client
86
+ def get_client(self, idp_name: str) -> Optional[OAuth2Client]:
87
+ return self._clients.get(idp_name)
90
88
 
91
89
  @classmethod
92
90
  def from_configs(cls, configs: Iterable[OAuth2ClientConfig]) -> "OAuth2Clients":
@@ -1,32 +1,32 @@
1
1
  {
2
- "_components-BG6v0EM8.js": {
3
- "file": "assets/components-BG6v0EM8.js",
2
+ "_components-Bs8eJEpU.js": {
3
+ "file": "assets/components-Bs8eJEpU.js",
4
4
  "name": "components",
5
5
  "imports": [
6
- "_vendor-BqTEkGQU.js",
7
- "_pages-DgaM7kpM.js",
8
- "_vendor-arizeai-DlOj0PQQ.js",
9
- "_vendor-codemirror-B2PHH5yZ.js",
6
+ "_vendor-D2eEI-6h.js",
7
+ "_pages-D-n2pkoG.js",
8
+ "_vendor-arizeai-kfOei7nf.js",
9
+ "_vendor-codemirror-1bq_t1Ec.js",
10
10
  "_vendor-three-BLWp5bic.js"
11
11
  ]
12
12
  },
13
- "_pages-DgaM7kpM.js": {
14
- "file": "assets/pages-DgaM7kpM.js",
13
+ "_pages-D-n2pkoG.js": {
14
+ "file": "assets/pages-D-n2pkoG.js",
15
15
  "name": "pages",
16
16
  "imports": [
17
- "_vendor-BqTEkGQU.js",
18
- "_components-BG6v0EM8.js",
19
- "_vendor-arizeai-DlOj0PQQ.js",
20
- "_vendor-codemirror-B2PHH5yZ.js",
21
- "_vendor-recharts-CKsi4IjN.js"
17
+ "_vendor-D2eEI-6h.js",
18
+ "_components-Bs8eJEpU.js",
19
+ "_vendor-arizeai-kfOei7nf.js",
20
+ "_vendor-codemirror-1bq_t1Ec.js",
21
+ "_vendor-recharts-DQ4xfrf4.js"
22
22
  ]
23
23
  },
24
24
  "_vendor-BGzfc4EU.css": {
25
25
  "file": "assets/vendor-BGzfc4EU.css",
26
26
  "src": "_vendor-BGzfc4EU.css"
27
27
  },
28
- "_vendor-BqTEkGQU.js": {
29
- "file": "assets/vendor-BqTEkGQU.js",
28
+ "_vendor-D2eEI-6h.js": {
29
+ "file": "assets/vendor-D2eEI-6h.js",
30
30
  "name": "vendor",
31
31
  "imports": [
32
32
  "_vendor-three-BLWp5bic.js"
@@ -35,39 +35,39 @@
35
35
  "assets/vendor-BGzfc4EU.css"
36
36
  ]
37
37
  },
38
- "_vendor-arizeai-DlOj0PQQ.js": {
39
- "file": "assets/vendor-arizeai-DlOj0PQQ.js",
38
+ "_vendor-arizeai-kfOei7nf.js": {
39
+ "file": "assets/vendor-arizeai-kfOei7nf.js",
40
40
  "name": "vendor-arizeai",
41
41
  "imports": [
42
- "_vendor-BqTEkGQU.js"
42
+ "_vendor-D2eEI-6h.js"
43
43
  ]
44
44
  },
45
- "_vendor-codemirror-B2PHH5yZ.js": {
46
- "file": "assets/vendor-codemirror-B2PHH5yZ.js",
45
+ "_vendor-codemirror-1bq_t1Ec.js": {
46
+ "file": "assets/vendor-codemirror-1bq_t1Ec.js",
47
47
  "name": "vendor-codemirror",
48
48
  "imports": [
49
- "_vendor-BqTEkGQU.js",
50
- "_vendor-shiki-DN26BkKE.js"
49
+ "_vendor-D2eEI-6h.js",
50
+ "_vendor-shiki-GGmcIQxA.js"
51
51
  ],
52
52
  "dynamicImports": [
53
- "_vendor-shiki-DN26BkKE.js",
54
- "_vendor-shiki-DN26BkKE.js",
55
- "_vendor-shiki-DN26BkKE.js"
53
+ "_vendor-shiki-GGmcIQxA.js",
54
+ "_vendor-shiki-GGmcIQxA.js",
55
+ "_vendor-shiki-GGmcIQxA.js"
56
56
  ]
57
57
  },
58
- "_vendor-recharts-CKsi4IjN.js": {
59
- "file": "assets/vendor-recharts-CKsi4IjN.js",
58
+ "_vendor-recharts-DQ4xfrf4.js": {
59
+ "file": "assets/vendor-recharts-DQ4xfrf4.js",
60
60
  "name": "vendor-recharts",
61
61
  "imports": [
62
- "_vendor-BqTEkGQU.js"
62
+ "_vendor-D2eEI-6h.js"
63
63
  ]
64
64
  },
65
- "_vendor-shiki-DN26BkKE.js": {
66
- "file": "assets/vendor-shiki-DN26BkKE.js",
65
+ "_vendor-shiki-GGmcIQxA.js": {
66
+ "file": "assets/vendor-shiki-GGmcIQxA.js",
67
67
  "name": "vendor-shiki",
68
68
  "isDynamicEntry": true,
69
69
  "imports": [
70
- "_vendor-BqTEkGQU.js"
70
+ "_vendor-D2eEI-6h.js"
71
71
  ]
72
72
  },
73
73
  "_vendor-three-BLWp5bic.js": {
@@ -75,19 +75,19 @@
75
75
  "name": "vendor-three"
76
76
  },
77
77
  "index.tsx": {
78
- "file": "assets/index-CSVcULw1.js",
78
+ "file": "assets/index-C6WEu5UP.js",
79
79
  "name": "index",
80
80
  "src": "index.tsx",
81
81
  "isEntry": true,
82
82
  "imports": [
83
- "_vendor-BqTEkGQU.js",
84
- "_vendor-arizeai-DlOj0PQQ.js",
85
- "_pages-DgaM7kpM.js",
86
- "_components-BG6v0EM8.js",
83
+ "_vendor-D2eEI-6h.js",
84
+ "_vendor-arizeai-kfOei7nf.js",
85
+ "_pages-D-n2pkoG.js",
86
+ "_components-Bs8eJEpU.js",
87
87
  "_vendor-three-BLWp5bic.js",
88
- "_vendor-codemirror-B2PHH5yZ.js",
89
- "_vendor-shiki-DN26BkKE.js",
90
- "_vendor-recharts-CKsi4IjN.js"
88
+ "_vendor-codemirror-1bq_t1Ec.js",
89
+ "_vendor-shiki-GGmcIQxA.js",
90
+ "_vendor-recharts-DQ4xfrf4.js"
91
91
  ]
92
92
  }
93
93
  }