Metadata-Version: 2.4
Name: flask-ip2proxy
Version: 1.0.0
Summary: Flask extension that flags proxy/VPN/Tor visitor IPs via IP2Proxy and caches the result in the session.
Author-email: IP2Location <support@ip2location.com>
License: MIT
Project-URL: Homepage, https://github.com/ip2location/flask-ip2proxy
Project-URL: Repository, https://github.com/ip2location/flask-ip2proxy
Project-URL: Issues, https://github.com/ip2location/flask-ip2proxy/issues
Keywords: flask,ip2proxy,proxy-detection,vpn-detection,ip-address,session,security
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Framework :: Flask
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Security
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE.TXT
Requires-Dist: Flask>=2.0
Requires-Dist: IP2Proxy>=3.6
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Dynamic: license-file

# Flask-IP2Proxy

A small Flask extension that checks whether a visitor's IP address is a
known proxy / VPN / Tor / data-center address using the
[IP2Proxy](https://pypi.org/project/IP2Proxy/) Python library, and stores
the result in the Flask **session** — so any other view or template in
the app can reuse it without repeating the lookup.

## Installation

From PyPI, once published:

```bash
pip install flask-ip2proxy
```

From this source tree (editable install, for development):

```bash
pip install -e ".[dev]"
```

`IP2Proxy` and `Flask` install automatically either way.

## How it works

- On the first request in a session, a `before_request` hook checks the
  visitor's IP against your local IP2Proxy `.BIN` database and stores the
  result under `session["ip2proxy"]`.
- On later requests, if the visitor's IP hasn't changed, the cached value
  is reused — no repeat database queries.
- Any view or Jinja template can read `session["ip2proxy"]` directly, or
  import the `current_proxy_info` proxy for convenience.

## Quick start

1. **Get a database file.** This extension requires a IP2Location database BIN file to work. You may obtain a free LITE database or purchase a commercial database as below:
   - Free, self-updating "LITE" editions: https://www.ip2location.com/database/lite
   - Full commercial editions with ISP/domain/etc: https://www.ip2location.com/database/ip2proxy

   Save the `.BIN` file somewhere your app can read it.

2. **Wire it up**

   ```python
   from flask import Flask
   from flask_ip2proxy import IP2ProxyFlask, current_proxy_info

   app = Flask(__name__)
   app.config["SECRET_KEY"] = "change-me"
   app.config["IP2PROXY_DB_PATH"] = "/path/to/IP2PROXY-LITE-PX1.BIN"

   ip2proxy = IP2ProxyFlask(app)

   @app.route("/")
   def index():
       if current_proxy_info and current_proxy_info["is_proxy_flagged"]:
           return "You appear to be using a proxy or VPN."
       return "Hello!"
   ```

   Or with the application-factory pattern:

   ```python
   ip2proxy = IP2ProxyFlask()

   def create_app():
       app = Flask(__name__)
       app.config["IP2PROXY_DB_PATH"] = "..."
       ip2proxy.init_app(app)
       return app
   ```

3. Try the full demo:

   ```bash
   pip install -e .
   # edit examples/example_app.py to point at your .BIN file
   python examples/example_app.py
   ```

   Or the quickstart-style version:

   ```bash
   python examples/hello_app.py
   # then visit http://127.0.0.1:5000/hello/ or /hello/YourName
   ```

## What's in the result

`session["ip2proxy"]` is a plain dict (or `None` if the lookup failed, was
skipped, or the IP couldn't be resolved). Two fields are always present
when a lookup succeeds:

| Key | Meaning |
|---|---|
| `is_proxy` | Raw code from the library: `-1` unknown/error (never stored — see below), `0` not a proxy, `1` a proxy, `2` a data-center/search-engine proxy. |
| `is_proxy_flagged` | Convenience boolean: `True` if `is_proxy` is `1` or `2`. This is the field most apps actually want. |

Everything else (`proxy_type`, `country_short`, `country_long`, `region`,
`city`, `isp`, `domain`, `usage_type`, `asn`, `as_name`, `last_seen`,
`threat`, `provider`, `fraud_score`, plus `ip`) is included automatically
whenever your `.BIN` edition supports it — see "Automatic column
detection" below.

A result with `is_proxy == -1` (IP2Proxy's way of saying "couldn't
determine this") is treated as a failed lookup and stored as `None`
rather than a dict full of placeholder text, matching how a private/local
IP or any other lookup failure is represented.

## Reading the data elsewhere

```python
from flask import session
session["ip2proxy"]                       # plain dict, or None
```

```python
from flask_ip2proxy import current_proxy_info
current_proxy_info["is_proxy_flagged"]     # LocalProxy, behaves like the dict
```

```jinja2
{{ session.ip2proxy.is_proxy_flagged }}
```

That last one needs no setup — Flask injects `session` into every
template automatically, so `session.ip2proxy.whatever` just works. See
the flask-ip2location README's "Reading the data elsewhere" section for
a fuller walkthrough of this (including a worked example extending
Flask's own quickstart `hello.html`, mirrored here in `examples/`).

## Automatic column detection

You don't need to know which `.BIN` edition you're using — the extension
detects available columns from the query result itself, the same idea as
Flask-IP2Location but adapted to how IP2Proxy actually reports it: rather
than only setting attributes for supported fields, IP2Proxy's `get_all()`
always returns every possible key, filling in the literal text
`"NOT SUPPORTED"` for anything your database edition doesn't cover. The
extension drops exactly those placeholder entries, so:

- A `PX2` LITE database gives you `is_proxy`, `is_proxy_flagged`,
  `proxy_type`, `country_short`, `country_long`, `ip` — nothing else,
  because that's all `PX2` contains.
- A higher `PX` tier or commercial edition additionally gives you
  `isp`, `domain`, `asn`, `fraud_score`, etc., automatically — nothing to
  configure, and nothing to update if you later upgrade your database.

If you want to keep only a subset regardless of what's available, set
`IP2PROXY_FIELDS` to an explicit list and it's applied as a filter on top
of auto-detection.

## Configuration reference

| Config key | Default | Description |
|---|---|---|
| `IP2PROXY_DB_PATH` | *required* | Path to the `.BIN` database file. |
| `IP2PROXY_SESSION_KEY` | `"ip2proxy"` | Session dict key the result is stored under. |
| `IP2PROXY_FIELDS` | `None` (auto) | Optional allow-list restricting which auto-detected fields are kept, e.g. `["is_proxy_flagged", "proxy_type"]`. |
| `IP2PROXY_AUTO_LOOKUP` | `True` | Automatically look up on every request via `before_request`. Set `False` and call `ip2proxy.refresh()` yourself to trigger it manually instead (e.g. only at login or checkout). |
| `IP2PROXY_SKIP_PRIVATE_IPS` | `True` | Skip the database query entirely for loopback/private/reserved IPs (e.g. `127.0.0.1` during local dev), storing `None` instead of a meaningless result. |
| `IP2PROXY_TRUST_PROXY_HEADER` | `False` | Trust the client-supplied `X-Forwarded-For` header for the visitor IP. See the caveat below — it's especially relevant here, since a visitor trying to evade proxy detection has every reason to spoof this exact header. |

## Notes and caveats

- **Proxies/load balancers.** If your app runs behind one,
  `request.remote_addr` will be the proxy's IP, not the visitor's — every
  visitor would then wrongly show up with your load balancer's own
  reputation. The correct fix is
  [`werkzeug.middleware.proxy_fix.ProxyFix`](https://werkzeug.palletsprojects.com/en/latest/middleware/proxy_fix/)
  configured for exactly as many trusted hops as you have, shown
  commented-out in `examples/example_app.py`. `IP2PROXY_TRUST_PROXY_HEADER`
  trusts whatever `X-Forwarded-For` value shows up instead, which a
  visitor can forge if nothing upstream is sanitizing it — for a
  security-relevant signal like proxy detection, get this right rather
  than reaching for the header-trusting shortcut outside of local testing.
- **This is a signal, not a verdict.** `is_proxy_flagged` reflects what a
  point-in-time IP-range database says about the address, not a live
  probe of the connection. Ranges get reassigned, home ISPs sometimes
  share ranges with hosting providers, and some legitimate users
  genuinely browse from VPNs. Treat it as one input alongside others
  (rate limiting, account history, etc.) rather than an automatic
  block/allow decision.
- **Session storage.** As with Flask-IP2Location, Flask's default session
  is a signed-but-not-encrypted cookie. Proxy-detection results are
  usually fine to store there, but if you widen `IP2PROXY_FIELDS` to
  include `fraud_score` or similar and have stricter requirements,
  consider a server-side session backend like
  [Flask-Session](https://flask-session.readthedocs.io/).
- **LITE database fields.** Free LITE editions only populate the columns
  their tier covers; anything else is detected automatically and dropped,
  as described above.
- **IPv6.** Whether IPv6 is resolved depends on which `.BIN` edition you
  download.

## Running the tests

```bash
pip install -e ".[dev]"
pytest
```

Tests monkeypatch `IP2Proxy.IP2Proxy` with a fake in `tests/conftest.py`
whose `get_all()` return shape was verified against the real library's
source (not guessed), so no real `.BIN` database file is needed to run
the suite.

## License

See the LICENSE file.
