diff --git a/authx/_internal/__init__.py b/authx/_internal/__init__.py index 1e8dff17..37f11d62 100644 --- a/authx/_internal/__init__.py +++ b/authx/_internal/__init__.py @@ -7,7 +7,6 @@ log_info, set_log_level, ) -from authx._internal._memory import MemoryIO from authx._internal._signature import SignatureSerializer from authx._internal._utils import ( RESERVED_CLAIMS, @@ -36,6 +35,5 @@ "utc", "end_of_day", "end_of_week", - "MemoryIO", "SignatureSerializer", ) diff --git a/authx/_internal/_memory.py b/authx/_internal/_memory.py deleted file mode 100644 index 0d9cdfb0..00000000 --- a/authx/_internal/_memory.py +++ /dev/null @@ -1,56 +0,0 @@ -import time -from typing import Any, Optional - - -class MemoryIO: - raw_memory_store: dict[str, dict[str, Any]] - - """ - MemoryIO is a class that implements the IO interface for the session store. - - It is used to store session data in memory. - """ - - def __init__(self) -> None: - """Initialize an instance of MemoryIO. - - Creates a dictionary to store the session data. - """ - self.raw_memory_store = {} - - async def has_session_id(self, session_id: str) -> bool: - return session_id in self.raw_memory_store - - async def has_no_session_id(self, session_id: str) -> bool: - return session_id not in self.raw_memory_store - - async def create_store(self, session_id: str) -> dict[str, Any]: - self.raw_memory_store[session_id] = { - "created_at": int(time.time()), - "store": {}, - } - await self.save_store(session_id) - return self.raw_memory_store.get(session_id, {}).get("store", {}) - - async def get_store(self, session_id: str) -> Optional[dict[str, Any]]: - if self.raw_memory_store.get(session_id): - return self.raw_memory_store.get(session_id, {}).get("store") - else: - return None - - async def save_store(self, session_id: str) -> None: - await self.get_store(session_id) - - async def gc(self) -> None: - if len(self.raw_memory_store) >= 100: - await self.cleanup_old_sessions() - - async def cleanup_old_sessions(self) -> None: - current_time = int(time.time()) - sessions_to_delete = [ - session_id - for session_id, session_info in self.raw_memory_store.items() - if current_time - session_info["created_at"] > 3600 * 12 - ] - for session_id in sessions_to_delete: - del self.raw_memory_store[session_id] diff --git a/docs/api/extra/cache.md b/docs/api/extra/cache.md index d0cd1791..5289d5f6 100644 --- a/docs/api/extra/cache.md +++ b/docs/api/extra/cache.md @@ -4,7 +4,7 @@ You need to install dependencies to use The HTTP Cache. ```console - $ pip install authx_extra[redis] + $ pip install authx_extra ``` ::: authx_extra.cache.HTTPCacheBackend diff --git a/docs/api/extra/metrics.md b/docs/api/extra/metrics.md index 4a1cc0b0..fe2e0b56 100644 --- a/docs/api/extra/metrics.md +++ b/docs/api/extra/metrics.md @@ -4,7 +4,7 @@ You need to install dependencies to use The Prometheus Metrics Middleware. ```console - $ pip install authx_extra[prometheus] + $ pip install authx_extra ``` ::: authx_extra.metrics.MetricsMiddleware diff --git a/docs/api/extra/oauth2.md b/docs/api/extra/oauth2.md index 5f7597c7..0567a6cf 100644 --- a/docs/api/extra/oauth2.md +++ b/docs/api/extra/oauth2.md @@ -1,3 +1,10 @@ # MiddlewareOauth2 +!!! warning + You need to install dependencies to use The Oauth2 Middleware. + + ```console + $ pip install authx_extra + ``` + ::: authx_extra.oauth2.MiddlewareOauth2 diff --git a/docs/api/extra/profiler.md b/docs/api/extra/profiler.md index 2f3b8870..7dbd1076 100644 --- a/docs/api/extra/profiler.md +++ b/docs/api/extra/profiler.md @@ -4,7 +4,7 @@ You need to install dependencies to use The Profiler. ```console - $ pip install authx_extra[profiler] + $ pip install authx_extra ``` ::: authx_extra.profiler.ProfilerMiddleware diff --git a/docs/api/extra/session.md b/docs/api/extra/session.md index e5ef9b97..84f4ce0a 100644 --- a/docs/api/extra/session.md +++ b/docs/api/extra/session.md @@ -1,3 +1,10 @@ # SessionIntegration +!!! warning + You need to install dependencies to use The Session Integration. + + ```console + $ pip install authx_extra + ``` + ::: authx_extra.session.SessionIntegration diff --git a/docs/api/internal/extra/memory.md b/docs/api/internal/extra/memory.md new file mode 100644 index 00000000..d6b5fc76 --- /dev/null +++ b/docs/api/internal/extra/memory.md @@ -0,0 +1,10 @@ +# MemoryIO + +!!! warning + You need to install dependencies to use The Memory Cache. + + ```console + $ pip install authx_extra + ``` + +::: authx_extra.extra._memory.MemoryIO diff --git a/docs/api/internal/memory.md b/docs/api/internal/memory.md deleted file mode 100644 index 080bda98..00000000 --- a/docs/api/internal/memory.md +++ /dev/null @@ -1,3 +0,0 @@ -# MemoryIO - -::: authx._internal._memory.MemoryIO diff --git a/mkdocs.yml b/mkdocs.yml index 6a98ce9b..a8be1dbe 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -230,8 +230,8 @@ nav: - Internal: - api/internal/callback.md - api/internal/errors.md - - api/internal/memory.md - api/internal/signature.md + - api/internal/extra/memory.md - Extra: - api/extra/session.md - api/extra/profiler.md diff --git a/tests/internal/test_memory.py b/tests/internal/test_memory.py deleted file mode 100644 index 085ab505..00000000 --- a/tests/internal/test_memory.py +++ /dev/null @@ -1,110 +0,0 @@ -from time import time - -import pytest - -from authx._internal import MemoryIO - - -@pytest.fixture -def memory_io(): - return MemoryIO() - - -@pytest.mark.asyncio -async def test_create_store(memory_io): - session_id = "123" - store = await memory_io.create_store(session_id) - assert store == {} - assert memory_io.raw_memory_store[session_id]["store"] == {} - - -@pytest.mark.asyncio -async def test_get_store_existing(memory_io): - session_id = "123" - memory_io.raw_memory_store[session_id] = { - "created_at": int(time()), - "store": {"key": "value"}, - } - store = await memory_io.get_store(session_id) - assert store == {"key": "value"} - - -@pytest.mark.asyncio -async def test_get_store_nonexistent(memory_io): - session_id = "123" - store = await memory_io.get_store(session_id) - assert store is None - - -@pytest.mark.asyncio -async def test_save_store(memory_io): - session_id = "123" - memory_io.raw_memory_store[session_id] = { - "created_at": int(time()), - "store": {"key": "value"}, - } - await memory_io.save_store(session_id) - assert await memory_io.get_store(session_id) == {"key": "value"} - - -@pytest.mark.asyncio -async def test_cleanup_old_sessions(memory_io): - current_time = int(time()) - memory_io.raw_memory_store = { - "1": {"created_at": current_time - 3600 * 12 - 1, "store": {}}, - "2": {"created_at": current_time - 3600 * 12, "store": {}}, - "3": {"created_at": current_time - 3600 * 12 + 1, "store": {}}, - } - await memory_io.cleanup_old_sessions() - expected_output = { - "2": {"created_at": current_time - 3600 * 12, "store": {}}, - "3": {"created_at": current_time - 3600 * 12 + 1, "store": {}}, - } - assert memory_io.raw_memory_store == expected_output - - -@pytest.mark.asyncio -async def test_has_session_id(): - store = MemoryIO() - await store.create_store("test-id") - assert await store.has_session_id("test-id") - assert not await store.has_no_session_id("test-id") - - -@pytest.mark.asyncio -async def test_get_store(): - store = MemoryIO() - await store.create_store("test-id") - assert await store.get_store("test-id") == {} - assert await store.get_store("nonexistent-id") is None - - -@pytest.mark.asyncio -async def populate_old_sessions(memory_io, count, created_at): - for i in range(count): - memory_io.raw_memory_store[str(i)] = { - "created_at": created_at, - "store": {}, - } - - -@pytest.mark.asyncio -async def test_gc_cleanup_old_sessions(memory_io): - # Populate raw_memory_store with 100 sessions older than 12 hours - current_time = int(time()) - twelve_hours_ago = current_time - 3600 * 12 - await populate_old_sessions(memory_io, 100, twelve_hours_ago) - - # Add one more session within 12 hours - extra_session_id = "1000" - memory_io.raw_memory_store[extra_session_id] = { - "created_at": current_time, - "store": {}, - } - - # Ensure gc triggers cleanup - await memory_io.gc() - - # Ensure old sessions are cleaned up - assert len(memory_io.raw_memory_store) == 101 - assert extra_session_id in memory_io.raw_memory_store diff --git a/tests/internal/test_signature.py b/tests/internal/test_signature.py deleted file mode 100644 index 1a5b85c8..00000000 --- a/tests/internal/test_signature.py +++ /dev/null @@ -1,89 +0,0 @@ -import time -from typing import Any - -import pytest - -from authx._internal import SignatureSerializer - - -@pytest.fixture -def serializer(): - return SignatureSerializer("TEST_SECRET_KEY", expired_in=1) - - -def test_encode_decode_success(serializer): - dict_obj = {"session_id": 1} - token = serializer.encode(dict_obj) - data, err = serializer.decode(token) - assert data is not None and err is None - assert data["session_id"] == 1 - - -def test_decode_expired_token(serializer): - dict_obj = {"session_id": 1} - token = serializer.encode(dict_obj) - time.sleep(2) # Wait for token to expire - data, err = serializer.decode(token) - assert data is None and err == "SignatureExpired" - - -def test_decode_invalid_token(serializer): - invalid_token = "invalid.token.here" - data, err = serializer.decode(invalid_token) - assert data is None and err == "InvalidSignature" - - -def test_decode_none_token(serializer): - data, err = serializer.decode(None) - assert data is None and err == "NoTokenSpecified" - - -@pytest.mark.xfail(reason="Test is currently failing due to unexpected behavior") -def test_decode_tampered_token(serializer): - dict_obj = {"session_id": 1} - token = serializer.encode(dict_obj) - tampered_token = token[:-1] + ("1" if token[-1] == "0" else "0") # Change last character - data, err = serializer.decode(tampered_token) - assert data is None and err == "InvalidSignature" - - -def test_no_expiration(): - non_expiring_serializer = SignatureSerializer("TEST_SECRET_KEY", expired_in=0) - dict_obj = {"session_id": 1} - token = non_expiring_serializer.encode(dict_obj) - time.sleep(2) # Wait, but token should not expire - data, err = non_expiring_serializer.decode(token) - assert data is not None and err is None - assert data["session_id"] == 1 - - -def test_different_secret_keys(): - serializer1 = SignatureSerializer("SECRET_KEY_1") - serializer2 = SignatureSerializer("SECRET_KEY_2") - dict_obj = {"session_id": 1} - token = serializer1.encode(dict_obj) - data, err = serializer2.decode(token) - assert data is None and err == "InvalidSignature" - - -def test_large_payload(): - serializer = SignatureSerializer("TEST_SECRET_KEY") - large_dict = {f"key_{i}": "x" * 1000 for i in range(100)} # 100 keys with 1000 character values - token = serializer.encode(large_dict) - data, err = serializer.decode(token) - assert data == large_dict and err is None - - -def test_empty_dict(): - serializer = SignatureSerializer("TEST_SECRET_KEY") - empty_dict: dict[str, Any] = {} - token = serializer.encode(empty_dict) - data, err = serializer.decode(token) - assert data == empty_dict and err is None - - -def test_malformed_token(): - serializer = SignatureSerializer("TEST_SECRET_KEY") - malformed_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." # Incomplete token - data, err = serializer.decode(malformed_token) - assert data is None and err == "BadSignature"