towerops/lib/towerops_web/plugs/detect_eu_user.ex
Graham McIntire 532d88ffb9
perf: disable Req retry for faster error tests
- Add retry: false to VISP Client HTTP requests
- Add retry: false to ReleaseChecker GitHub API requests
- Remove unused Plug.Conn import from RemoteIpLogger
- Remove unused default parameter from req_get/2

Results:
- VISP sync test: 7007ms → 113ms (62x faster)
- ReleaseChecker test: 7004ms → 17ms (402x faster)
- UserResetPasswordLive test: 1495ms → 284ms (5x faster)

Req's default retry behavior (1s, 2s, 4s exponential backoff) was
causing 7-second delays for HTTP 500/503 error responses in tests.
For these clients, immediate failure is preferred over retries.
2026-03-10 16:19:31 -05:00

85 lines
2.2 KiB
Elixir

defmodule ToweropsWeb.Plugs.DetectEUUser do
@moduledoc """
Detects if a user is from an EU/EEA country that requires GDPR cookie consent.
Uses local GeoIP database to determine the user's country from their IP address.
The result is stored in conn.assigns.requires_cookie_consent
"""
import Plug.Conn
alias ToweropsWeb.RemoteIp
# EU/EEA country codes that require GDPR cookie consent
# EU member states + EEA countries (Norway, Iceland, Liechtenstein)
@eu_eea_countries ~w(
AT BE BG HR CY CZ DK EE FI FR DE GR HU IE IT LV LT LU MT NL PL PT RO SK SI ES SE
IS NO LI
)
# British overseas territories and Crown dependencies also covered by GDPR
@uk_territories ~w(
GB GG JE IM GI
)
@doc false
def init(opts), do: opts
@doc false
def call(conn, _opts) do
require Logger
# Fetch cookies to check for existing consent
conn = fetch_cookies(conn)
requires_consent = detect_eu_user(conn)
# Store in process dictionary so layouts can access it
Process.put(:requires_cookie_consent, requires_consent)
conn
|> put_session(:requires_cookie_consent, requires_consent)
|> assign(:requires_cookie_consent, requires_consent)
end
defp detect_eu_user(conn) do
# If user has already accepted cookies, don't show banner
if has_consent_cookie?(conn) do
false
else
# In development (localhost), always show the banner for testing
hostname = conn.host
if hostname in ["localhost", "127.0.0.1"] do
true
else
detect_country_from_headers(conn)
end
end
end
defp has_consent_cookie?(conn) do
case conn.cookies["cookie_consent"] do
"accepted" -> true
_ -> false
end
end
defp detect_country_from_headers(conn) do
# Get remote IP from connection
remote_ip = RemoteIp.from_conn(conn) || "0.0.0.0"
case Towerops.GeoIP.lookup(remote_ip) do
country_code when is_binary(country_code) ->
eu_country?(country_code)
nil ->
# No country found - default to showing banner (conservative approach)
true
end
end
defp eu_country?(country_code) do
country_code = String.upcase(country_code)
country_code in @eu_eea_countries or country_code in @uk_territories
end
end