- 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.
85 lines
2.2 KiB
Elixir
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
|