diff --git a/fia_api/core/auth/tokens.py b/fia_api/core/auth/tokens.py index 4308c525..72b7f4f7 100644 --- a/fia_api/core/auth/tokens.py +++ b/fia_api/core/auth/tokens.py @@ -10,13 +10,11 @@ from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from fia_api.core.auth import AUTH_URL -from fia_api.core.cache import cache_get_json, cache_set_json, hash_key from fia_api.core.exceptions import AuthError logger = logging.getLogger(__name__) DEV_MODE = bool(os.environ.get("DEV_MODE", False)) # noqa: PLW1508 -AUTH_VERIFY_CACHE_TTL_SECONDS = int(os.environ.get("AUTH_VERIFY_CACHE_TTL_SECONDS", "60")) @dataclass @@ -79,15 +77,9 @@ def _is_jwt_access_token_valid(access_token: str) -> bool: """ logger.info("Checking if JWT access token is valid") try: - cache_key = f"fia_api:auth:verify:{hash_key(access_token)}" - cached = cache_get_json(cache_key) - if cached is True: - return True - response = requests.post(f"{AUTH_URL}/verify", json={"token": access_token}, timeout=30) if response.status_code == HTTPStatus.OK: logger.info("JWT was valid") - cache_set_json(cache_key, True, AUTH_VERIFY_CACHE_TTL_SECONDS) return True return False except RuntimeError: diff --git a/fia_api/core/cache.py b/fia_api/core/cache.py index 8ac6fd64..47a0f5fe 100644 --- a/fia_api/core/cache.py +++ b/fia_api/core/cache.py @@ -2,7 +2,6 @@ from __future__ import annotations -import hashlib import json import logging import os @@ -64,6 +63,17 @@ def _create_client() -> Redis | None: def get_valkey_client() -> Redis | None: + """ + Get or create a Valkey (Redis) client instance. + + Returns a shared Redis client if Valkey is configured and available. + The client is lazily initialized on first access and cached for reuse. + If the connection fails or Valkey is not configured, it returns None and + disables further connection attempts. + + :return: Redis client instance if available, None otherwise + """ + state = _valkey_state() if state.disabled: return None @@ -87,6 +97,17 @@ def _disable_cache(exc: Exception) -> None: def cache_get_json(key: str) -> Any | None: + """ + Retrieve and deserialize a JSON value from the Valkey cache. + + Attempts to fetch a cached value by key and parse it as JSON. If the cache + is unavailable, the key doesn't exist, or the value cannot be parsed as JSON, + returns None. Automatically disables the cache on connection errors. + + :param key: The cache key to retrieve + :return: Deserialized JSON value if found and valid, None otherwise + """ + client = get_valkey_client() if client is None: return None @@ -110,6 +131,20 @@ def cache_get_json(key: str) -> Any | None: def cache_set_json(key: str, value: Any, ttl_seconds: int) -> None: + """ + Store a JSON-serializable value in the Valkey cache with a time-to-live. + + Serializes the provided value to JSON and stores it in the cache with an + expiration time. If the cache is unavailable, the value cannot be serialized + to JSON, or the TTL is non-positive, the operation is silently skipped. + Automatically disables the cache on connection errors. + + :param key: The cache key under which to store the value + :param value: Any JSON-serializable value to cache + :param ttl_seconds: Time-to-live in seconds; must be positive + :return: None + """ + if ttl_seconds <= 0: return client = get_valkey_client() @@ -123,7 +158,3 @@ def cache_set_json(key: str, value: Any, ttl_seconds: int) -> None: client.setex(key, ttl_seconds, payload) except RedisError as exc: _disable_cache(exc) - - -def hash_key(value: str) -> str: - return hashlib.sha256(value.encode("utf-8")).hexdigest() diff --git a/test/core/auth/test_tokens.py b/test/core/auth/test_tokens.py index 6cc73986..9b7c08bd 100644 --- a/test/core/auth/test_tokens.py +++ b/test/core/auth/test_tokens.py @@ -64,48 +64,6 @@ def test_is_jwt_access_token_valid_raises_returns_invalid(mock_post): assert not jwtbearer._is_jwt_access_token_valid(TOKEN) -def test_is_jwt_access_token_valid_uses_cache_short_circuit(): - """Test cached token avoids network request.""" - with ( - patch("fia_api.core.auth.tokens.cache_get_json", return_value=True) as mock_cache, - patch("fia_api.core.auth.tokens.requests.post") as mock_post, - ): - jwtbearer = JWTAPIBearer() - assert jwtbearer._is_jwt_access_token_valid(TOKEN) - mock_cache.assert_called_once() - mock_post.assert_not_called() - - -def test_is_jwt_access_token_valid_sets_cache_on_success(): - """Test cache set on successful verification.""" - response = Mock() - response.status_code = HTTPStatus.OK - with ( - patch("fia_api.core.auth.tokens.cache_get_json", return_value=None), - patch("fia_api.core.auth.tokens.cache_set_json") as mock_cache_set, - patch("fia_api.core.auth.tokens.requests.post", return_value=response), - patch("fia_api.core.auth.tokens.hash_key", return_value="abc123"), - patch("fia_api.core.auth.tokens.AUTH_VERIFY_CACHE_TTL_SECONDS", 120), - ): - jwtbearer = JWTAPIBearer() - assert jwtbearer._is_jwt_access_token_valid(TOKEN) - mock_cache_set.assert_called_once_with("fia_api:auth:verify:abc123", True, 120) - - -def test_is_jwt_access_token_valid_does_not_cache_on_failure(): - """Test cache not set when verification fails.""" - response = Mock() - response.status_code = HTTPStatus.FORBIDDEN - with ( - patch("fia_api.core.auth.tokens.cache_get_json", return_value=None), - patch("fia_api.core.auth.tokens.cache_set_json") as mock_cache_set, - patch("fia_api.core.auth.tokens.requests.post", return_value=response), - ): - jwtbearer = JWTAPIBearer() - assert not jwtbearer._is_jwt_access_token_valid(TOKEN) - mock_cache_set.assert_not_called() - - def test_is_api_token_valid_check_against_env_var(): api_key = str(mock.MagicMock()) os.environ["FIA_API_API_KEY"] = api_key diff --git a/test/core/test_cache.py b/test/core/test_cache.py index a4a7e49b..1cb2faf7 100644 --- a/test/core/test_cache.py +++ b/test/core/test_cache.py @@ -12,7 +12,6 @@ cache_get_json, cache_set_json, get_valkey_client, - hash_key, ) TTL_SECONDS = 30 @@ -201,7 +200,3 @@ def test_cache_get_json_returns_none_if_raw_is_none(): mock_client.get.return_value = None with patch("fia_api.core.cache.get_valkey_client", return_value=mock_client): assert cache_get_json("key") is None - - -def test_hash_key_returns_sha256_hex(): - assert hash_key("abc") == "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"