Skip to content

Commit e56bac5

Browse files
rustyconoverclaude
andcommitted
Improve OAuth diagnostics and bump version to 0.1.24
Include offending values in OAuthResourceMetadata validation errors and add diagnostic logging for JWT claim mismatches on authentication failure. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4470a27 commit e56bac5

4 files changed

Lines changed: 37 additions & 10 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "vgi-rpc"
3-
version = "0.1.23"
3+
version = "0.1.24"
44
description = "Vector Gateway Interface - RPC framework based on Apache Arrow"
55
readme = "README.md"
66
requires-python = ">=3.13"

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vgi_rpc/http/_oauth.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,23 +86,23 @@ def __post_init__(self) -> None:
8686
raise ValueError("OAuthResourceMetadata.authorization_servers must contain at least one entry")
8787
if self.client_id is not None and not _URL_SAFE_RE.fullmatch(self.client_id):
8888
raise ValueError(
89-
"OAuthResourceMetadata.client_id must contain only URL-safe characters "
90-
"(alphanumeric, hyphen, underscore, period, tilde)"
89+
f"OAuthResourceMetadata.client_id must contain only URL-safe characters "
90+
f"(alphanumeric, hyphen, underscore, period, tilde), got: {self.client_id!r}"
9191
)
9292
if self.client_secret is not None and not _URL_SAFE_RE.fullmatch(self.client_secret):
9393
raise ValueError(
94-
"OAuthResourceMetadata.client_secret must contain only URL-safe characters "
95-
"(alphanumeric, hyphen, underscore, period, tilde)"
94+
f"OAuthResourceMetadata.client_secret must contain only URL-safe characters "
95+
f"(alphanumeric, hyphen, underscore, period, tilde), got: {self.client_secret!r}"
9696
)
9797
if self.device_code_client_id is not None and not _URL_SAFE_RE.fullmatch(self.device_code_client_id):
9898
raise ValueError(
99-
"OAuthResourceMetadata.device_code_client_id must contain only URL-safe characters "
100-
"(alphanumeric, hyphen, underscore, period, tilde)"
99+
f"OAuthResourceMetadata.device_code_client_id must contain only URL-safe characters "
100+
f"(alphanumeric, hyphen, underscore, period, tilde), got: {self.device_code_client_id!r}"
101101
)
102102
if self.device_code_client_secret is not None and not _URL_SAFE_RE.fullmatch(self.device_code_client_secret):
103103
raise ValueError(
104-
"OAuthResourceMetadata.device_code_client_secret must contain only URL-safe characters "
105-
"(alphanumeric, hyphen, underscore, period, tilde)"
104+
f"OAuthResourceMetadata.device_code_client_secret must contain only URL-safe characters "
105+
f"(alphanumeric, hyphen, underscore, period, tilde), got: {self.device_code_client_secret!r}"
106106
)
107107

108108
def to_json_dict(self) -> dict[str, object]:

vgi_rpc/http/_oauth_jwt.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@
1212

1313
from __future__ import annotations
1414

15+
import logging
1516
import threading
1617
from collections.abc import Callable, Mapping
1718
from typing import Any
1819

20+
logger = logging.getLogger(__name__)
21+
1922
import falcon
2023
import httpx
2124

@@ -105,6 +108,28 @@ def _get_key_set(force_refresh: bool = False) -> _KeySet:
105108
if claims_options:
106109
base_claims_options.update(claims_options)
107110

111+
def _log_claim_mismatch(token: str, keys: _KeySet, exc: JoseError) -> None:
112+
"""Log expected vs actual claims on JWT validation failure."""
113+
try:
114+
# Decode without validation to inspect the payload
115+
raw_claims = jwt.decode(token, keys)
116+
token_aud = raw_claims.get("aud")
117+
token_iss = raw_claims.get("iss")
118+
logger.warning(
119+
"JWT validation failed: %s\n"
120+
" expected iss: %s\n"
121+
" token iss: %s\n"
122+
" expected aud (any of): %s\n"
123+
" token aud: %s",
124+
exc,
125+
issuer,
126+
token_iss,
127+
list(audiences),
128+
token_aud,
129+
)
130+
except Exception:
131+
logger.warning("JWT validation failed: %s (could not decode token for diagnostics)", exc)
132+
108133
def authenticate(req: falcon.Request) -> AuthContext:
109134
auth_header = req.get_header("Authorization") or ""
110135
if not auth_header.startswith("Bearer "):
@@ -123,9 +148,11 @@ def authenticate(req: falcon.Request) -> AuthContext:
123148
claims = jwt.decode(token, keys, claims_options=base_claims_options)
124149
claims.validate()
125150
except JoseError as exc:
151+
_log_claim_mismatch(token, keys, exc)
126152
raise ValueError(f"Invalid JWT: {exc}") from exc
127153
except JoseError as exc:
128154
# Expired tokens, bad claims, etc. — no point refreshing keys
155+
_log_claim_mismatch(token, keys, exc)
129156
raise ValueError(f"Invalid JWT: {exc}") from exc
130157

131158
principal = str(claims.get(principal_claim, ""))

0 commit comments

Comments
 (0)