lib/canonical_host.ex

defmodule CanonicalHost do
  @moduledoc """
  See [README](readme.html) for usage.
  """

  @behaviour Plug

  @impl true
  def init(opts) do
    opts = Keyword.validate!(opts, [:config_key])

    [config_key: Keyword.get(opts, :config_key, :default)]
  end

  @impl true
  def call(%Plug.Conn{method: "GET"} = conn, config_key: config_key) do
    case Application.get_env(:canonical_host, config_key) do
      nil ->
        conn

      opts ->
        opts = Keyword.validate!(opts, [:additional_hosts, :host, :scheme])
        do_call(conn, Keyword.get(opts, :host), opts)
    end
  end

  def call(conn, _), do: conn

  defp do_call(conn, nil = _host, _opts), do: conn
  defp do_call(%Plug.Conn{host: host} = conn, host, _opts), do: conn

  defp do_call(conn, host, opts) do
    additional_hosts = Keyword.get(opts, :additional_hosts, [])
    scheme = Keyword.get(opts, :scheme, "https")

    if conn.host in additional_hosts do
      conn
    else
      location = "#{scheme}://#{host}#{conn.request_path}#{query_suffix(conn.query_string)}"

      conn
      |> Phoenix.Controller.redirect(external: location)
      |> Plug.Conn.halt()
    end
  end

  defp query_suffix(""), do: ""
  defp query_suffix(query_string), do: "?#{query_string}"
end