Files
stack/sdks/implementations/python/src/stack_auth/models/data_vault.py
T
Ejiro Asiuwhu 37118e0871 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.
2026-03-26 09:27:21 +01:00

110 lines
3.8 KiB
Python

"""Data vault store classes for server-side key-value storage."""
from __future__ import annotations
from typing import Any
from stack_auth.errors import NotFoundError
# Only suppress key-not-found errors; store-not-found should propagate.
_KEY_NOT_FOUND_CODE = "DATA_VAULT_STORE_HASHED_KEY_DOES_NOT_EXIST"
def _is_key_not_found(err: NotFoundError) -> bool:
"""Return True if the error is specifically a missing key, not a missing store."""
return getattr(err, "code", None) == _KEY_NOT_FOUND_CODE
class DataVaultStore:
"""Synchronous data vault store -- a server-side key-value store.
Obtained via ``StackServerApp.get_data_vault_store(store_id)``.
"""
def __init__(self, store_id: str, *, _client: Any) -> None:
self.id = store_id
self._client = _client
self._base_path = f"/data-vault/stores/{store_id}/items"
def get(self, key: str) -> str | None:
"""Get the value for a key, or ``None`` if not found."""
try:
data = self._client.request("GET", f"{self._base_path}/{key}")
except NotFoundError as err:
if _is_key_not_found(err):
return None
raise
if data is None:
return None
return data.get("value") if isinstance(data, dict) else data
def set(self, key: str, value: str) -> None:
"""Set or update the value for a key."""
self._client.request("PUT", f"{self._base_path}/{key}", body={"value": value})
def delete(self, key: str) -> None:
"""Delete a key-value pair. No error if key doesn't exist."""
try:
self._client.request("DELETE", f"{self._base_path}/{key}")
except NotFoundError as err:
if _is_key_not_found(err):
return
raise
def list_keys(self) -> list[str]:
"""Return all keys in the store."""
data = self._client.request("GET", self._base_path)
if data is None:
return []
# Response may be {"items": [...]} or a list directly
if isinstance(data, dict):
return data.get("items", [])
return data
class AsyncDataVaultStore:
"""Asynchronous data vault store -- a server-side key-value store.
Obtained via ``AsyncStackServerApp.get_data_vault_store(store_id)``.
"""
def __init__(self, store_id: str, *, _client: Any) -> None:
self.id = store_id
self._client = _client
self._base_path = f"/data-vault/stores/{store_id}/items"
async def get(self, key: str) -> str | None:
"""Get the value for a key, or ``None`` if not found."""
try:
data = await self._client.request("GET", f"{self._base_path}/{key}")
except NotFoundError as err:
if _is_key_not_found(err):
return None
raise
if data is None:
return None
return data.get("value") if isinstance(data, dict) else data
async def set(self, key: str, value: str) -> None:
"""Set or update the value for a key."""
await self._client.request("PUT", f"{self._base_path}/{key}", body={"value": value})
async def delete(self, key: str) -> None:
"""Delete a key-value pair. No error if key doesn't exist."""
try:
await self._client.request("DELETE", f"{self._base_path}/{key}")
except NotFoundError as err:
if _is_key_not_found(err):
return
raise
async def list_keys(self) -> list[str]:
"""Return all keys in the store."""
data = await self._client.request("GET", self._base_path)
if data is None:
return []
# Response may be {"items": [...]} or a list directly
if isinstance(data, dict):
return data.get("items", [])
return data