fix(proxy): make auth_v2 import-safe and lock its deps so CI/startup don't break

Importing proxy_server registers the auth_v2 router, which pulled casbin in at
module load via enforcer.py - so the proxy failed to even start in any env
without casbin (CI, minimal installs), regardless of whether auth_v2 was enabled.
An opt-in feature must not be a hard import.

- enforcer.py imports casbin lazily inside CasbinEnforcer.__init__; authlib in
  jwt_verifier.py is imported lazily inside the verify/JWKS functions. The package
  (and the startup router) now import with both deps absent; they are required
  only when auth_v2 actually builds an enforcer or verifies a JWT.
- casbin and authlib were declared in pyproject but missing from uv.lock, so
  `uv sync --frozen` never installed them. Regenerated the lock (adds casbin,
  authlib, joserfc, simpleeval) so the deps install where the feature runs.

Verified: the v2 package imports with casbin+authlib blocked, and building an
enforcer raises a clear ImportError only when casbin is genuinely absent.
This commit is contained in:
ryan-crabbe-berri 2026-06-05 11:17:39 -07:00
parent fd638b484a
commit 6f9d906abe
3 changed files with 60 additions and 6 deletions

View file

@ -1,8 +1,6 @@
import os
from typing import List, Optional, Sequence
import casbin
_MODEL_PATH = os.path.join(os.path.dirname(__file__), "model.conf")
Rule = Sequence[str]
@ -24,6 +22,11 @@ class CasbinEnforcer:
resource_groupings: Optional[List[Rule]] = None,
domain_groupings: Optional[List[Rule]] = None,
):
# Imported lazily so merely importing the proxy (or this package, e.g. to
# register the policy-admin router) never requires casbin. The dependency
# is only needed once auth_v2 is actually enabled and builds an enforcer.
import casbin
self._enforcer = casbin.Enforcer(_MODEL_PATH)
self._enforcer.enable_auto_save(False)
for rule in policies:

View file

@ -1,9 +1,6 @@
import time
from typing import Any, Dict, List, Optional
from authlib.jose import JsonWebKey, JsonWebToken
from authlib.jose.errors import JoseError
class JWTVerificationError(Exception):
"""Raised when a token fails signature or standard-claim validation."""
@ -55,6 +52,9 @@ def verify(
network). Raises :class:`JWTVerificationError` on any failure so callers
never branch on authlib's internal exception types.
"""
from authlib.jose import JsonWebToken
from authlib.jose.errors import JoseError
decoder = JsonWebToken(algorithms or _DEFAULT_ALGORITHMS)
try:
claims = decoder.decode(
@ -83,6 +83,7 @@ class JWKSProvider:
return self._key_set
import httpx
from authlib.jose import JsonWebKey
async with httpx.AsyncClient() as client:
response = await client.get(self.jwks_uri)

52
uv.lock generated
View file

@ -9,7 +9,7 @@ resolution-markers = [
]
[options]
exclude-newer = "2026-05-28T03:32:27.927695Z"
exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values.
exclude-newer-span = "P3D"
[manifest]
@ -423,6 +423,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a2/1f/aa74b23b6eea4cf9b79ace914df59123c4c8e7e4bd32dd22d09c126422d9/aurelio_sdk-0.0.19-py3-none-any.whl", hash = "sha256:390c0212b59ce99116df8722d3badced88c5ef0bb742a6222d479ceed0ed3948", size = 17322, upload-time = "2025-03-24T14:37:31.305Z" },
]
[[package]]
name = "authlib"
version = "1.7.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cryptography" },
{ name = "joserfc" },
]
sdist = { url = "https://files.pythonhosted.org/packages/36/98/7d93f30d029643c0275dbc0bd6d5a6f670661ee6c9a94d93af7ab4887600/authlib-1.7.2.tar.gz", hash = "sha256:2cea25fefcd4e7173bdf1372c0afc265c8034b23a8cd5dcb6a9164b826c64231", size = 176511, upload-time = "2026-05-06T08:10:23.116Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fb/95/adcb68e20c34162e9135f370d6e31737719c2b6f94bc953fe7ed1f10fe21/authlib-1.7.2-py2.py3-none-any.whl", hash = "sha256:3e1faedc9d87e7d56a164eca3ccb6ace0d61b94abe83e92242f8dc8bba9b4a9f", size = 259548, upload-time = "2026-05-06T08:10:21.436Z" },
]
[[package]]
name = "azure-ai-contentsafety"
version = "1.0.0"
@ -655,6 +668,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/bf/0f/f897abe4ea0a8c408ae65c8c83bffab4936ad65d6032d4fb4cd35bbdc3ee/cachetools-7.1.1-py3-none-any.whl", hash = "sha256:0335cd7a0952d2b22327441fb0628139e234c565559eeb91a8a4ac7551c5353d", size = 16775, upload-time = "2026-05-03T20:00:27.857Z" },
]
[[package]]
name = "casbin"
version = "1.43.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "simpleeval" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ad/df/ff2aa55cf0d7c14622ce4f9252cdc34c828c81d4213965d73207ac5434ae/casbin-1.43.0.tar.gz", hash = "sha256:d2e90ce8e72f912877851e94d37999f32c558c6ba7aba0437d483275262e86e0", size = 425727, upload-time = "2025-05-10T06:57:18.902Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/08/07/facef6abd81378e6153b757dc1848621675d971fbc88ebb5d182ebc1c37f/casbin-1.43.0-py3-none-any.whl", hash = "sha256:63a3d1228870250e859ccd94133fe478821093f71dd37f05e0baa0c6fea26623", size = 475059, upload-time = "2025-05-10T06:57:16.89Z" },
]
[[package]]
name = "certifi"
version = "2026.4.22"
@ -2735,6 +2760,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" },
]
[[package]]
name = "joserfc"
version = "1.7.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cryptography" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d3/c3/2f590052b55cbdd0ace470ee7ee1f685f6882051be93a9374891005623e2/joserfc-1.7.0.tar.gz", hash = "sha256:4aced6ab0c47846f0a531402aec2419a874b91e918df9c4c9da8a82fb559d6c4", size = 232967, upload-time = "2026-06-02T09:59:34.506Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5b/83/b6b62a66a06ce872d9429a5eb5ee20b2002fd9c331b953c94381c1f7c9f9/joserfc-1.7.0-py3-none-any.whl", hash = "sha256:17e5d7a5a35e65442b05efc435a3d5d46696ffa2c8a2ed0eea6f63fc268e3224", size = 70387, upload-time = "2026-06-02T09:59:33.264Z" },
]
[[package]]
name = "jsonlines"
version = "4.0.0"
@ -3318,10 +3355,12 @@ mlflow = [
]
proxy = [
{ name = "apscheduler" },
{ name = "authlib" },
{ name = "azure-identity" },
{ name = "azure-storage-blob" },
{ name = "backoff" },
{ name = "boto3" },
{ name = "casbin" },
{ name = "cryptography" },
{ name = "fastapi" },
{ name = "fastapi-sso" },
@ -3472,6 +3511,7 @@ requires-dist = [
{ name = "apscheduler", marker = "extra == 'proxy'", specifier = ">=3.11.2,<4.0" },
{ name = "audioread", marker = "extra == 'stt-nvidia-riva'", specifier = ">=3.0.1" },
{ name = "aurelio-sdk", marker = "python_full_version < '3.14' and extra == 'semantic-router'", specifier = ">=0.0.19,<1.0" },
{ name = "authlib", marker = "extra == 'proxy'", specifier = ">=1.6.5,<2.0" },
{ name = "azure-ai-contentsafety", marker = "extra == 'proxy-runtime'", specifier = ">=1.0.0,<2.0" },
{ name = "azure-identity", marker = "extra == 'extra-proxy'", specifier = ">=1.25.2,<2.0" },
{ name = "azure-identity", marker = "extra == 'proxy'", specifier = ">=1.25.2,<2.0" },
@ -3480,6 +3520,7 @@ requires-dist = [
{ name = "azure-storage-file-datalake", marker = "extra == 'proxy-runtime'", specifier = ">=12.20.0,<13.0" },
{ name = "backoff", marker = "extra == 'proxy'", specifier = ">=2.2.1,<3.0" },
{ name = "boto3", marker = "extra == 'proxy'", specifier = ">=1.43.1,<2.0" },
{ name = "casbin", marker = "extra == 'proxy'", specifier = ">=1.43.0,<2.0" },
{ name = "click", specifier = ">=8.0.0,<9.0" },
{ name = "cryptography", marker = "extra == 'proxy'", specifier = ">=46.0.7,<47.0" },
{ name = "ddtrace", marker = "extra == 'proxy-runtime'", specifier = ">=2.19.0,<3.0" },
@ -7055,6 +7096,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
]
[[package]]
name = "simpleeval"
version = "1.0.7"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b4/9d/e7c9309940794dd3073cba2e5101df5874d84243595ce63b1e1c8f9b9c76/simpleeval-1.0.7.tar.gz", hash = "sha256:1e10e5f9fec597814444e20c0892ed15162fa214c8a88f434b5b077cf2fef85b", size = 30250, upload-time = "2026-03-16T10:53:03.464Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0f/2f/f32aa85591882378bb43caa09363f3ed97df399369a5144c7f19f2275bc0/simpleeval-1.0.7-py3-none-any.whl", hash = "sha256:97ac271bfd8f2af9e7b9a36ceea67617f26fa873f9d5ae1922f64d4c1442534b", size = 18792, upload-time = "2026-03-16T10:53:02.103Z" },
]
[[package]]
name = "six"
version = "1.17.0"