diff --git a/.mike.yml b/.mike.yml index 0762c4a..3743ab2 100644 --- a/.mike.yml +++ b/.mike.yml @@ -2,6 +2,7 @@ version_selector: true title_switch: true versions_file: docs/versions/versions.json versions: + - 4.2.1 - 4.2.0 - 4.1.2 - 4.1.0 @@ -31,4 +32,4 @@ versions: - 0.3.2 - latest aliases: - latest: 4.2.0 \ No newline at end of file + latest: 4.2.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cd14bd..4239627 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,16 @@ Release Notes ___ +v4.2.1 (2025-11-05) +------------------- + +Bug Fixes (v4.2.1) +------------ + +- **IPInfo redirect URLs**: IPInfo API sometimes responds with 302 code, and by not handling the redirect, the database would not be downloaded. Now, `IPInfoManager` class follows redirects. + +___ + v4.2.0 (2025-10-16) ------------------- diff --git a/docs/index.md b/docs/index.md index 48f0755..b874788 100644 --- a/docs/index.md +++ b/docs/index.md @@ -75,7 +75,7 @@ You can also download the example app as a Docker container from [GitHub Contain docker pull ghcr.io/rennf93/fastapi-guard-example:latest # Or pull a specific version (matches library releases) -docker pull ghcr.io/rennf93/fastapi-guard-example:v4.2.0 +docker pull ghcr.io/rennf93/fastapi-guard-example:v4.2.1 ``` ___ diff --git a/docs/release-notes.md b/docs/release-notes.md index 2cea04d..5c1cf4b 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -10,6 +10,16 @@ Release Notes ___ +v4.2.1 (2025-11-05) +------------------- + +Bug Fixes (v4.2.1) +------------ + +- **IPInfo redirect URLs**: IPInfo API sometimes responds with 302 code, and by not handling the redirect, the database would not be downloaded. Now, `IPInfoManager` class follows redirects. + +___ + v4.2.0 (2025-10-16) ------------------- diff --git a/docs/versions/versions.json b/docs/versions/versions.json index c848cc6..321e49d 100644 --- a/docs/versions/versions.json +++ b/docs/versions/versions.json @@ -1,4 +1,5 @@ { + "4.2.1": "4.2.1", "4.2.0": "4.2.0", "4.1.2": "4.1.2", "4.1.0": "4.1.0", @@ -27,5 +28,5 @@ "0.3.4": "0.3.4", "0.3.3": "0.3.3", "0.3.2": "0.3.2", - "latest": "4.2.0" + "latest": "4.2.1" } \ No newline at end of file diff --git a/guard/handlers/ipinfo_handler.py b/guard/handlers/ipinfo_handler.py index 442726f..ac22273 100644 --- a/guard/handlers/ipinfo_handler.py +++ b/guard/handlers/ipinfo_handler.py @@ -127,7 +127,7 @@ async def _download_database(self) -> None: async with httpx.AsyncClient() as session: for attempt in range(retries): try: - response = await session.get(url) + response = await session.get(url, follow_redirects=True) response.raise_for_status() with open(self.db_path, "wb") as f: f.write(response.content) diff --git a/pyproject.toml b/pyproject.toml index 9acbe40..b7d1dce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "fastapi_guard" -version = "4.2.0" +version = "4.2.1" description = "A security library for FastAPI to control IPs, log requests, and detect penetration attempts." authors = [ {name = "Renzo Franceschini", email = "rennf93@users.noreply.github.com"} diff --git a/tests/test_ipinfo/test_ipinfo.py b/tests/test_ipinfo/test_ipinfo.py index dc47858..8a668d0 100644 --- a/tests/test_ipinfo/test_ipinfo.py +++ b/tests/test_ipinfo/test_ipinfo.py @@ -298,3 +298,41 @@ async def test_get_country_result_without_country(tmp_path: Path) -> None: def test_ipinfo_not_initialized() -> None: db = IPInfoManager(token="test") assert db.is_initialized is False + + +@pytest.mark.asyncio +async def test_redirect_handling(tmp_path: Path) -> None: + """Test that redirects are properly followed during download""" + db = IPInfoManager(token="test", db_path=tmp_path / "test.mmdb") + + # Create mock redirect response (302) + redirect_response = Mock() + redirect_response.status_code = 302 + redirect_response.headers = { + "Location": "https://ipinfo.io/data/free/country_asn.mmdb" + } + + # Create final response after redirect + final_response = Mock() + final_response.raise_for_status = Mock() + final_response.content = b"valid_db_content" + final_response.status_code = 200 + # httpx tracks redirect history + final_response.history = [redirect_response] + + with patch("httpx.AsyncClient.get", return_value=final_response) as mock_get: + await db._download_database() + + # Verify the file was written with final response content + assert db.db_path.exists() + with open(db.db_path, "rb") as f: + assert f.read() == b"valid_db_content" + + # Verify follow_redirects=True was used (which enables redirect handling) + mock_get.assert_called_once() + call_kwargs = mock_get.call_args[1] + assert call_kwargs.get("follow_redirects") is True + + # Verify the response has redirect history + assert len(final_response.history) == 1 + assert final_response.history[0].status_code == 302