Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .mike.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -31,4 +32,4 @@ versions:
- 0.3.2
- latest
aliases:
latest: 4.2.0
latest: 4.2.1
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
-------------------

Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

___
Expand Down
10 changes: 10 additions & 0 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
-------------------

Expand Down
3 changes: 2 additions & 1 deletion docs/versions/versions.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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"
}
2 changes: 1 addition & 1 deletion guard/handlers/ipinfo_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"}
Expand Down
38 changes: 38 additions & 0 deletions tests/test_ipinfo/test_ipinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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