68 lines
1.9 KiB
Elixir
68 lines
1.9 KiB
Elixir
defmodule DiffuserWeb.Plugs.PublicIp do
|
|
@moduledoc "Get public IP address of request from x-forwarded-for header"
|
|
|
|
def init(opts), do: opts
|
|
|
|
def call(%{assigns: %{ip: _}} = conn, _opts), do: conn
|
|
|
|
def call(conn, _opts) do
|
|
process(conn, Plug.Conn.get_req_header(conn, "x-forwarded-for"))
|
|
end
|
|
|
|
def process(%{remote_ip: remote_ip} = conn, []) do
|
|
Plug.Conn.assign(conn, :ip, to_string(:inet_parse.ntoa(remote_ip)))
|
|
end
|
|
|
|
def process(conn, vals) do
|
|
# Rewrite standard remote_ip field with value from header
|
|
ip_address = get_ip_address(conn, vals)
|
|
# See https://hexdocs.pm/plug/Plug.Conn.html
|
|
conn = %{conn | remote_ip: ip_address}
|
|
|
|
Plug.Conn.assign(conn, :ip, to_string(:inet_parse.ntoa(ip_address)))
|
|
end
|
|
|
|
defp get_ip_address(conn, vals)
|
|
defp get_ip_address(%{remote_ip: remote_ip}, []), do: remote_ip
|
|
|
|
defp get_ip_address(%{remote_ip: remote_ip} = _conn, [val | _]) do
|
|
# Split into multiple values
|
|
comps =
|
|
val
|
|
|> String.split(~r{\s*,\s*}, trim: true)
|
|
# Get rid of "unknown" values
|
|
|> Enum.filter(&(&1 != "unknown"))
|
|
# Split IP from port, if any
|
|
|> Enum.map(&hd(String.split(&1, ":")))
|
|
# Filter out blanks
|
|
|> Enum.filter(&(&1 != ""))
|
|
# Parse address into :inet.ip_address tuple
|
|
|> Enum.map(&parse_address(&1))
|
|
# Elminate internal IP addreses, e.g. 192.168.1.1
|
|
|> Enum.filter(&is_public_ip(&1))
|
|
|
|
case comps do
|
|
[] -> remote_ip
|
|
[comp | _] -> comp
|
|
end
|
|
end
|
|
|
|
defp parse_address(ip) do
|
|
case :inet.parse_ipv4strict_address(to_charlist(ip)) do
|
|
{:ok, ip_address} -> ip_address
|
|
{:error, :einval} -> :einval
|
|
end
|
|
end
|
|
|
|
defp is_public_ip(ip_address) do
|
|
case ip_address do
|
|
{10, _, _, _} -> false
|
|
{192, 168, _, _} -> false
|
|
{172, second, _, _} when second >= 16 and second <= 31 -> false
|
|
{127, 0, 0, _} -> false
|
|
{_, _, _, _} -> true
|
|
:einval -> false
|
|
end
|
|
end
|
|
end
|