GeoIP / geo-blocking
As of 0.37.0 DaloyJS ships geoBlock()— a country allow/deny middleware that maps the client IP to a country and rejects (or logs) traffic from countries you don't serve. It is the compliance/abuse counterpart to ipRestriction() and ipReputation().
- No bundled database— Daloy ships no GeoIP data and adds no runtime dependency. You supply the IP → country mapping (a MaxMind reader, an
ip2locationreader, your own table) or read a country header injected by your edge. - Two strategies, pick one —
lookupCountry(ip)when you own the lookup, orresolveCountry(ctx)when an upstream already attached the country. - Fail-closed allow-lists — when an
allowlist is configured, an unknowncountry is rejected by default (it's not on the list). Deny-only configurations fail open. Both are overridable withallowUnknownCountry. - Reuses trusted-proxy IP resolution — the same
X-Forwarded-For/X-Real-IPhandling (off by default, opt in withtrustProxyHeaders) as the other network guards.
Strategy 1 — bring your own IP → country lookup
Use any GeoIP reader as an operator dependency. Daloy resolves the client IP and hands it to lookupCountry; return an ISO 3166-1 alpha-2 code (or nothing when the IP can't be mapped).
Strategy 2 — read an edge-injected country header
If your app runs behind a CDN or platform that already geolocates the request, skip the IP lookup entirely and read the header. No proxy-trust configuration is needed because you are not parsing X-Forwarded-For yourself.
Deployment-platform country headers
Most edges expose the resolved country as a request header, which makes resolveCountry a one-liner. Common values:
On platforms that do not inject a country header (a bare Node / Bun / Deno deployment, or a VPS), use Strategy 1 with a local MaxMind database and trustProxyHeadersmatched to your proxy chain. Cloudflare's CF-IPCountry can also be XX (unknown) or T1 (Tor) — those are treated as an unknown country unless you list them explicitly.
Allow-list vs. deny-list semantics
- deny — listed countries are always rejected; a deny match wins over an allow match (least privilege).
- allow — when non-empty, only listed countries pass; everything else (including an unresolved country) is rejected.
- both — deny is evaluated first, then the allow-list gate.
Country codes are case-insensitive and validated at construction — a typo like "USA" throws immediately rather than silently never matching.
Unknown countries
When the country can't be resolved (no IP, no mapping, empty header), the default is:
- allow-list configured → blocked (fail closed).
- deny-only → allowed (fail open).
Override either way with allowUnknownCountry.
Monitoring before enforcing
Roll out safely with mode: "log": requests are never blocked, but onBlock fires for every would-be block so you can measure impact first.
Reading the country downstream
For allowed requests, the resolved country is stamped on ctx.state.geo (rename with stateKey), so handlers can localise or audit without a second lookup.
Rejection response
A blocked request throws ForbiddenError, rendered as RFC 9457 application/problem+json with HTTP 403 and Cache-Control: no-store. The default message ("Access from your region is not permitted") is configurable via message and deliberately does not echo the country or IP back to the client.
Security notes
- Geo-blocking is a compliance / abuse-reduction tool, not an authentication control. VPNs and proxies defeat it; pair it with real auth.
- Only set
trustProxyHeaderswhen every request reaches Daloy through a proxy chain you control — otherwiseX-Forwarded-Foris attacker-spoofable. - Keep your GeoIP database current; stale data misclassifies reassigned ranges.