vpndetection-fastapi 2.1.2__tar.gz → 2.1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {vpndetection_fastapi-2.1.2 → vpndetection_fastapi-2.1.4}/CHANGELOG.md +12 -0
- {vpndetection_fastapi-2.1.2 → vpndetection_fastapi-2.1.4}/PKG-INFO +3 -3
- {vpndetection_fastapi-2.1.2 → vpndetection_fastapi-2.1.4}/README.md +1 -1
- {vpndetection_fastapi-2.1.2 → vpndetection_fastapi-2.1.4}/pyproject.toml +1 -1
- {vpndetection_fastapi-2.1.2 → vpndetection_fastapi-2.1.4}/src/vpndetection_fastapi/__init__.py +8 -7
- {vpndetection_fastapi-2.1.2 → vpndetection_fastapi-2.1.4}/tests/test_middleware.py +16 -0
- {vpndetection_fastapi-2.1.2 → vpndetection_fastapi-2.1.4}/.github/workflows/ci.yml +0 -0
- {vpndetection_fastapi-2.1.2 → vpndetection_fastapi-2.1.4}/.github/workflows/release.yml +0 -0
- {vpndetection_fastapi-2.1.2 → vpndetection_fastapi-2.1.4}/.gitignore +0 -0
- {vpndetection_fastapi-2.1.2 → vpndetection_fastapi-2.1.4}/LICENSE +0 -0
- {vpndetection_fastapi-2.1.2 → vpndetection_fastapi-2.1.4}/src/vpndetection_fastapi/py.typed +0 -0
- {vpndetection_fastapi-2.1.2 → vpndetection_fastapi-2.1.4}/testdata/bogons.json +0 -0
- {vpndetection_fastapi-2.1.2 → vpndetection_fastapi-2.1.4}/testdata/testdata.json +0 -0
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
What each release changed for you, newest first. Each line is a commit's summary, linked to its full description and diff. Releases before 2.0.5 are described by their release commits.
|
|
4
4
|
|
|
5
|
+
## 2.1.4 - 2026-10-15
|
|
6
|
+
|
|
7
|
+
### Fixes
|
|
8
|
+
|
|
9
|
+
- Require vpndetection 5.7.2: Retry-After, the key, the base URL and unreadable answers ([`4d74c45`](https://github.com/vpndetection-io/sdk-python-fastapi/commit/4d74c456bb11419cb181d5f7c911913a84398ca3))
|
|
10
|
+
|
|
11
|
+
## 2.1.3 - 2026-10-10
|
|
12
|
+
|
|
13
|
+
### Fixes
|
|
14
|
+
|
|
15
|
+
- Require vpndetection 5.7.1: the spec re-pinned to 2026.10.09 ([`62563b2`](https://github.com/vpndetection-io/sdk-python-fastapi/commit/62563b2ec2b31e6b317d836c229f939bb9f0ec30))
|
|
16
|
+
|
|
5
17
|
## 2.1.2 - 2026-10-06
|
|
6
18
|
|
|
7
19
|
### Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: vpndetection-fastapi
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.4
|
|
4
4
|
Summary: Official FastAPI and Starlette middleware for the VPNDetection API. Detect VPNs, proxies, Tor, hosting and CDN visitors on every request.
|
|
5
5
|
Project-URL: Homepage, https://vpndetection.io
|
|
6
6
|
Project-URL: Source, https://github.com/vpndetection-io/sdk-python-fastapi
|
|
@@ -20,7 +20,7 @@ Classifier: Topic :: Security
|
|
|
20
20
|
Classifier: Typing :: Typed
|
|
21
21
|
Requires-Python: >=3.11
|
|
22
22
|
Requires-Dist: starlette>=0.37
|
|
23
|
-
Requires-Dist: vpndetection<6,>=5.7.
|
|
23
|
+
Requires-Dist: vpndetection<6,>=5.7.2
|
|
24
24
|
Provides-Extra: dev
|
|
25
25
|
Requires-Dist: fastapi>=0.115; extra == 'dev'
|
|
26
26
|
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
@@ -112,7 +112,7 @@ A refused request gets an `HTTPException`, `403` with `{"detail": "access denied
|
|
|
112
112
|
|
|
113
113
|
This is the setting that decides whether any of the above works, and it is the one thing only you can get right.
|
|
114
114
|
|
|
115
|
-
By default the middleware uses `request.client.host
|
|
115
|
+
By default the middleware uses `request.client.host`. uvicorn, and `fastapi run` with it, reads proxy headers by default, but only from a peer that `--forwarded-allow-ips` trusts, and that defaults to `127.0.0.1,::1` (or `$FORWARDED_ALLOW_IPS`). So behind a proxy on the same machine, the visitor's address arrives with no setup. Behind one anywhere else, such as nginx in its own container or a load balancer in a VPC, every visitor looks like the proxy. That address is usually a private one, which the middleware answers locally: nobody is flagged, nothing is blocked, and the private-address warning below is the only sign. Name your proxy, by its address or its subnet: `--forwarded-allow-ips=172.28.5.0/28`. gunicorn's own `--forwarded-allow-ips` works the same way. Never pass `*`. It trusts every peer, so the left-most `X-Forwarded-For` entry wins, and that is whatever the visitor sent: someone on a VPN who sends `X-Forwarded-For: 1.1.1.1` is looked up as `1.1.1.1`.
|
|
116
116
|
|
|
117
117
|
For an edge that writes the address into its own header, name the header:
|
|
118
118
|
|
|
@@ -82,7 +82,7 @@ A refused request gets an `HTTPException`, `403` with `{"detail": "access denied
|
|
|
82
82
|
|
|
83
83
|
This is the setting that decides whether any of the above works, and it is the one thing only you can get right.
|
|
84
84
|
|
|
85
|
-
By default the middleware uses `request.client.host
|
|
85
|
+
By default the middleware uses `request.client.host`. uvicorn, and `fastapi run` with it, reads proxy headers by default, but only from a peer that `--forwarded-allow-ips` trusts, and that defaults to `127.0.0.1,::1` (or `$FORWARDED_ALLOW_IPS`). So behind a proxy on the same machine, the visitor's address arrives with no setup. Behind one anywhere else, such as nginx in its own container or a load balancer in a VPC, every visitor looks like the proxy. That address is usually a private one, which the middleware answers locally: nobody is flagged, nothing is blocked, and the private-address warning below is the only sign. Name your proxy, by its address or its subnet: `--forwarded-allow-ips=172.28.5.0/28`. gunicorn's own `--forwarded-allow-ips` works the same way. Never pass `*`. It trusts every peer, so the left-most `X-Forwarded-For` entry wins, and that is whatever the visitor sent: someone on a VPN who sends `X-Forwarded-For: 1.1.1.1` is looked up as `1.1.1.1`.
|
|
86
86
|
|
|
87
87
|
For an edge that writes the address into its own header, name the header:
|
|
88
88
|
|
{vpndetection_fastapi-2.1.2 → vpndetection_fastapi-2.1.4}/src/vpndetection_fastapi/__init__.py
RENAMED
|
@@ -45,7 +45,7 @@ __all__ = [
|
|
|
45
45
|
"xff_ip_selector",
|
|
46
46
|
]
|
|
47
47
|
|
|
48
|
-
__version__ = "2.1.
|
|
48
|
+
__version__ = "2.1.4"
|
|
49
49
|
|
|
50
50
|
# Set on a request ``skip`` claimed, so block_if can tell it from one the middleware
|
|
51
51
|
# never saw.
|
|
@@ -58,13 +58,14 @@ _SELECTORS: Selectors[Request] = bind_selectors(
|
|
|
58
58
|
)
|
|
59
59
|
)
|
|
60
60
|
|
|
61
|
-
#: ``request.client.host
|
|
62
|
-
#:
|
|
61
|
+
#: ``request.client.host``. uvicorn reads proxy headers into it by default, but only
|
|
62
|
+
#: from a peer ``--forwarded-allow-ips`` trusts, which defaults to ``127.0.0.1,::1``.
|
|
63
63
|
#:
|
|
64
|
-
#: Behind a
|
|
65
|
-
#:
|
|
66
|
-
#:
|
|
67
|
-
#:
|
|
64
|
+
#: Behind a proxy anywhere else, every visitor looks like the proxy, usually a private
|
|
65
|
+
#: address that is answered locally, so nobody is flagged and the private-address
|
|
66
|
+
#: warning is the only sign. Pass ``--forwarded-allow-ips=<your proxy's address or
|
|
67
|
+
#: subnet>``, never ``*``, which takes the left-most ``X-Forwarded-For`` entry a visitor
|
|
68
|
+
#: can forge, or use :func:`header_ip_selector`.
|
|
68
69
|
default_ip_selector: IpSelector[Request] = _SELECTORS.default
|
|
69
70
|
|
|
70
71
|
#: An address from ``X-Forwarded-For``.
|
|
@@ -8,6 +8,7 @@ to arrive wearing a public address, through a selector.
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import json
|
|
11
|
+
import os
|
|
11
12
|
import pathlib
|
|
12
13
|
from typing import Any
|
|
13
14
|
|
|
@@ -359,3 +360,18 @@ def test_corpus_conditions_through_block_if(case: dict[str, Any]) -> None:
|
|
|
359
360
|
assert len(reported) == (1 if case["expect"]["missing"] else 0), case["why"]
|
|
360
361
|
for member in case["expect"]["missing"]:
|
|
361
362
|
assert member in reported[0], case["why"]
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
# Built exactly as the README builds it: an API key and nothing else. Every other test hands
|
|
366
|
+
# the middleware a client, so none of them could see a core that cannot start from a key
|
|
367
|
+
# alone. The test client's loopback peer is a bogon, answered with no request.
|
|
368
|
+
def test_the_middleware_starts_the_way_the_readme_builds_it(
|
|
369
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
370
|
+
) -> None:
|
|
371
|
+
monkeypatch.setenv("VPNDETECTION_API_KEY", "a-key")
|
|
372
|
+
status, body = get(app_with(api_key=os.environ["VPNDETECTION_API_KEY"]))
|
|
373
|
+
|
|
374
|
+
assert status == 200
|
|
375
|
+
assert body["attached"] is True
|
|
376
|
+
assert body["is_bogon"] is True
|
|
377
|
+
assert body["error"] is None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|