defmodule MicrowavepropWeb.Plugs.RemoteIp do @moduledoc """ Extracts the client IP from X-Forwarded-For (set by nginx/dokku proxy) and assigns it to conn.remote_ip and Logger metadata. """ @behaviour Plug @impl true def init(opts), do: opts @impl true # Cloudflare sets Cf-Connecting-Ip with the real client IP. # Fall back to X-Forwarded-For for non-tunneled requests (dev, direct). @ip_headers ["cf-connecting-ip", "x-forwarded-for"] def call(conn, _opts) do ip = Enum.find_value(@ip_headers, fn header -> case Plug.Conn.get_req_header(conn, header) do [value | _] -> value |> String.split(",") |> hd() |> String.trim() |> parse_ip() _ -> nil end end) conn = if ip do %{conn | remote_ip: ip} else conn end Logger.metadata(remote_ip: format_ip(conn.remote_ip)) conn end defp parse_ip(str) do case :inet.parse_address(String.to_charlist(str)) do {:ok, ip} -> ip _ -> nil end end defp format_ip({a, b, c, d}), do: "#{a}.#{b}.#{c}.#{d}" defp format_ip({a, b, c, d, e, f, g, h}), do: "#{a}:#{b}:#{c}:#{d}:#{e}:#{f}:#{g}:#{h}" defp format_ip(_), do: "unknown" end