Skip to content

Commit 37118e0

Browse files
committed
fix: skip aud/iss verification when not provided and narrow data vault error handling
- PyJWT defaults verify_aud and verify_iss to True, which rejects tokens with iss/aud claims when no expected value is passed. Now explicitly disables these checks when audience/issuer are not provided. - Data vault get/delete now only suppress key-not-found errors (DATA_VAULT_STORE_HASHED_KEY_DOES_NOT_EXIST), letting store-not-found errors (DATA_VAULT_STORE_DOES_NOT_EXIST) propagate as they should.
1 parent 5cbad57 commit 37118e0

2 files changed

Lines changed: 36 additions & 10 deletions

File tree

sdks/implementations/python/src/stack_auth/_jwt.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,16 +169,21 @@ async def async_verify_token(
169169
key = await fetcher.get_signing_key(kid)
170170

171171
kwargs: dict[str, Any] = {}
172+
options: dict[str, bool] = {"verify_exp": True}
172173
if audience is not None:
173174
kwargs["audience"] = audience
175+
else:
176+
options["verify_aud"] = False
174177
if issuer is not None:
175178
kwargs["issuer"] = issuer
179+
else:
180+
options["verify_iss"] = False
176181

177182
return jwt.decode( # type: ignore[no-any-return]
178183
token,
179184
key,
180185
algorithms=ALLOWED_ALGORITHMS,
181-
options={"verify_exp": True},
186+
options=options,
182187
**kwargs,
183188
)
184189

@@ -218,15 +223,20 @@ def sync_verify_token(
218223
key = fetcher.get_signing_key(kid)
219224

220225
kwargs: dict[str, Any] = {}
226+
options: dict[str, bool] = {"verify_exp": True}
221227
if audience is not None:
222228
kwargs["audience"] = audience
229+
else:
230+
options["verify_aud"] = False
223231
if issuer is not None:
224232
kwargs["issuer"] = issuer
233+
else:
234+
options["verify_iss"] = False
225235

226236
return jwt.decode( # type: ignore[no-any-return]
227237
token,
228238
key,
229239
algorithms=ALLOWED_ALGORITHMS,
230-
options={"verify_exp": True},
240+
options=options,
231241
**kwargs,
232242
)

sdks/implementations/python/src/stack_auth/models/data_vault.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@
66

77
from stack_auth.errors import NotFoundError
88

9+
# Only suppress key-not-found errors; store-not-found should propagate.
10+
_KEY_NOT_FOUND_CODE = "DATA_VAULT_STORE_HASHED_KEY_DOES_NOT_EXIST"
11+
12+
13+
def _is_key_not_found(err: NotFoundError) -> bool:
14+
"""Return True if the error is specifically a missing key, not a missing store."""
15+
return getattr(err, "code", None) == _KEY_NOT_FOUND_CODE
16+
917

1018
class DataVaultStore:
1119
"""Synchronous data vault store -- a server-side key-value store.
@@ -22,8 +30,10 @@ def get(self, key: str) -> str | None:
2230
"""Get the value for a key, or ``None`` if not found."""
2331
try:
2432
data = self._client.request("GET", f"{self._base_path}/{key}")
25-
except NotFoundError:
26-
return None
33+
except NotFoundError as err:
34+
if _is_key_not_found(err):
35+
return None
36+
raise
2737
if data is None:
2838
return None
2939
return data.get("value") if isinstance(data, dict) else data
@@ -36,8 +46,10 @@ def delete(self, key: str) -> None:
3646
"""Delete a key-value pair. No error if key doesn't exist."""
3747
try:
3848
self._client.request("DELETE", f"{self._base_path}/{key}")
39-
except NotFoundError:
40-
pass
49+
except NotFoundError as err:
50+
if _is_key_not_found(err):
51+
return
52+
raise
4153

4254
def list_keys(self) -> list[str]:
4355
"""Return all keys in the store."""
@@ -65,8 +77,10 @@ async def get(self, key: str) -> str | None:
6577
"""Get the value for a key, or ``None`` if not found."""
6678
try:
6779
data = await self._client.request("GET", f"{self._base_path}/{key}")
68-
except NotFoundError:
69-
return None
80+
except NotFoundError as err:
81+
if _is_key_not_found(err):
82+
return None
83+
raise
7084
if data is None:
7185
return None
7286
return data.get("value") if isinstance(data, dict) else data
@@ -79,8 +93,10 @@ async def delete(self, key: str) -> None:
7993
"""Delete a key-value pair. No error if key doesn't exist."""
8094
try:
8195
await self._client.request("DELETE", f"{self._base_path}/{key}")
82-
except NotFoundError:
83-
pass
96+
except NotFoundError as err:
97+
if _is_key_not_found(err):
98+
return
99+
raise
84100

85101
async def list_keys(self) -> list[str]:
86102
"""Return all keys in the store."""

0 commit comments

Comments
 (0)