defmodule TarakanWeb.Plugs.ForwardedProto do @moduledoc """ Rewrites `conn.scheme` from a trusted reverse proxy's `X-Forwarded-Proto`. Only applies when: 1. `:trusted_proxies` is configured (CIDR strings or IPs), and 2. the direct TCP peer is inside that set. Must run before `Plug.SSL`: behind a TLS-terminating proxy every request arrives as plain HTTP, so `Plug.SSL` can only tell external HTTPS from HTTP through this header - and the header is only meaningful from a peer we trust. Untrusted peers keep the real scheme, so a direct client cannot suppress the HTTPS redirect or HSTS by forging `X-Forwarded-Proto`. """ @behaviour Plug import Plug.Conn alias TarakanWeb.Plugs.ClientIp @impl true def init(opts), do: opts @impl true def call(conn, _opts) do proxies = Application.get_env(:tarakan, :trusted_proxies, []) if proxies != [] and ClientIp.trusted_ip?(conn.remote_ip, proxies) do rewrite_scheme(conn) else conn end end # Mirrors Plug.RewriteOn's :x_forwarded_proto semantics. defp rewrite_scheme(conn) do case get_req_header(conn, "x-forwarded-proto") do ["https" | _rest] -> %{conn | scheme: :https} ["http" | _rest] -> %{conn | scheme: :http} _other -> conn end end end