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.
- {arize_phoenix-12.2.0.dist-info → arize_phoenix-12.3.0.dist-info}/METADATA +1 -1
- {arize_phoenix-12.2.0.dist-info → arize_phoenix-12.3.0.dist-info}/RECORD +22 -21
- phoenix/server/api/auth_messages.py +46 -0
- phoenix/server/api/routers/oauth2.py +48 -28
- phoenix/server/app.py +5 -0
- phoenix/server/cost_tracking/model_cost_manifest.json +54 -54
- phoenix/server/oauth2.py +2 -4
- phoenix/server/static/.vite/manifest.json +39 -39
- phoenix/server/static/assets/{components-BG6v0EM8.js → components-Bs8eJEpU.js} +422 -377
- phoenix/server/static/assets/{index-CSVcULw1.js → index-C6WEu5UP.js} +12 -12
- phoenix/server/static/assets/{pages-DgaM7kpM.js → pages-D-n2pkoG.js} +542 -488
- phoenix/server/static/assets/vendor-D2eEI-6h.js +914 -0
- phoenix/server/static/assets/{vendor-arizeai-DlOj0PQQ.js → vendor-arizeai-kfOei7nf.js} +2 -2
- phoenix/server/static/assets/{vendor-codemirror-B2PHH5yZ.js → vendor-codemirror-1bq_t1Ec.js} +3 -3
- phoenix/server/static/assets/{vendor-recharts-CKsi4IjN.js → vendor-recharts-DQ4xfrf4.js} +1 -1
- phoenix/server/static/assets/{vendor-shiki-DN26BkKE.js → vendor-shiki-GGmcIQxA.js} +1 -1
- phoenix/server/templates/index.html +1 -0
- phoenix/version.py +1 -1
- phoenix/server/static/assets/vendor-BqTEkGQU.js +0 -903
- {arize_phoenix-12.2.0.dist-info → arize_phoenix-12.3.0.dist-info}/WHEEL +0 -0
- {arize_phoenix-12.2.0.dist-info → arize_phoenix-12.3.0.dist-info}/entry_points.txt +0 -0
- {arize_phoenix-12.2.0.dist-info → arize_phoenix-12.3.0.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-12.2.0.dist-info → arize_phoenix-12.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
431
|
-
phoenix/server/static/assets/components-
|
|
432
|
-
phoenix/server/static/assets/index-
|
|
433
|
-
phoenix/server/static/assets/pages-
|
|
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-
|
|
436
|
-
phoenix/server/static/assets/vendor-arizeai-
|
|
437
|
-
phoenix/server/static/assets/vendor-codemirror-
|
|
438
|
-
phoenix/server/static/assets/vendor-recharts-
|
|
439
|
-
phoenix/server/static/assets/vendor-shiki-
|
|
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=
|
|
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.
|
|
480
|
-
arize_phoenix-12.
|
|
481
|
-
arize_phoenix-12.
|
|
482
|
-
arize_phoenix-12.
|
|
483
|
-
arize_phoenix-12.
|
|
484
|
-
arize_phoenix-12.
|
|
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=
|
|
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=
|
|
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="
|
|
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
|
|
166
|
-
|
|
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
|
-
|
|
170
|
-
|
|
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
|
|
177
|
-
|
|
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
|
|
188
|
-
|
|
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:
|
|
585
|
+
def _redirect_to_login(*, request: Request, error: AuthErrorCode) -> RedirectResponse:
|
|
564
586
|
"""
|
|
565
|
-
Creates a RedirectResponse to the login page to display an error
|
|
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
|
-
|
|
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-
|
|
3
|
-
"file": "assets/components-
|
|
2
|
+
"_components-Bs8eJEpU.js": {
|
|
3
|
+
"file": "assets/components-Bs8eJEpU.js",
|
|
4
4
|
"name": "components",
|
|
5
5
|
"imports": [
|
|
6
|
-
"_vendor-
|
|
7
|
-
"_pages-
|
|
8
|
-
"_vendor-arizeai-
|
|
9
|
-
"_vendor-codemirror-
|
|
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-
|
|
14
|
-
"file": "assets/pages-
|
|
13
|
+
"_pages-D-n2pkoG.js": {
|
|
14
|
+
"file": "assets/pages-D-n2pkoG.js",
|
|
15
15
|
"name": "pages",
|
|
16
16
|
"imports": [
|
|
17
|
-
"_vendor-
|
|
18
|
-
"_components-
|
|
19
|
-
"_vendor-arizeai-
|
|
20
|
-
"_vendor-codemirror-
|
|
21
|
-
"_vendor-recharts-
|
|
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-
|
|
29
|
-
"file": "assets/vendor-
|
|
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-
|
|
39
|
-
"file": "assets/vendor-arizeai-
|
|
38
|
+
"_vendor-arizeai-kfOei7nf.js": {
|
|
39
|
+
"file": "assets/vendor-arizeai-kfOei7nf.js",
|
|
40
40
|
"name": "vendor-arizeai",
|
|
41
41
|
"imports": [
|
|
42
|
-
"_vendor-
|
|
42
|
+
"_vendor-D2eEI-6h.js"
|
|
43
43
|
]
|
|
44
44
|
},
|
|
45
|
-
"_vendor-codemirror-
|
|
46
|
-
"file": "assets/vendor-codemirror-
|
|
45
|
+
"_vendor-codemirror-1bq_t1Ec.js": {
|
|
46
|
+
"file": "assets/vendor-codemirror-1bq_t1Ec.js",
|
|
47
47
|
"name": "vendor-codemirror",
|
|
48
48
|
"imports": [
|
|
49
|
-
"_vendor-
|
|
50
|
-
"_vendor-shiki-
|
|
49
|
+
"_vendor-D2eEI-6h.js",
|
|
50
|
+
"_vendor-shiki-GGmcIQxA.js"
|
|
51
51
|
],
|
|
52
52
|
"dynamicImports": [
|
|
53
|
-
"_vendor-shiki-
|
|
54
|
-
"_vendor-shiki-
|
|
55
|
-
"_vendor-shiki-
|
|
53
|
+
"_vendor-shiki-GGmcIQxA.js",
|
|
54
|
+
"_vendor-shiki-GGmcIQxA.js",
|
|
55
|
+
"_vendor-shiki-GGmcIQxA.js"
|
|
56
56
|
]
|
|
57
57
|
},
|
|
58
|
-
"_vendor-recharts-
|
|
59
|
-
"file": "assets/vendor-recharts-
|
|
58
|
+
"_vendor-recharts-DQ4xfrf4.js": {
|
|
59
|
+
"file": "assets/vendor-recharts-DQ4xfrf4.js",
|
|
60
60
|
"name": "vendor-recharts",
|
|
61
61
|
"imports": [
|
|
62
|
-
"_vendor-
|
|
62
|
+
"_vendor-D2eEI-6h.js"
|
|
63
63
|
]
|
|
64
64
|
},
|
|
65
|
-
"_vendor-shiki-
|
|
66
|
-
"file": "assets/vendor-shiki-
|
|
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-
|
|
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-
|
|
78
|
+
"file": "assets/index-C6WEu5UP.js",
|
|
79
79
|
"name": "index",
|
|
80
80
|
"src": "index.tsx",
|
|
81
81
|
"isEntry": true,
|
|
82
82
|
"imports": [
|
|
83
|
-
"_vendor-
|
|
84
|
-
"_vendor-arizeai-
|
|
85
|
-
"_pages-
|
|
86
|
-
"_components-
|
|
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-
|
|
89
|
-
"_vendor-shiki-
|
|
90
|
-
"_vendor-recharts-
|
|
88
|
+
"_vendor-codemirror-1bq_t1Ec.js",
|
|
89
|
+
"_vendor-shiki-GGmcIQxA.js",
|
|
90
|
+
"_vendor-recharts-DQ4xfrf4.js"
|
|
91
91
|
]
|
|
92
92
|
}
|
|
93
93
|
}
|