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
