lib/ex_imaginary.ex

defmodule ExImaginary do
  @moduledoc """
  Provides functions to generate secure Imginary URLs.
  """

  defp config do
    %{
      host: Application.get_env(:ex_imaginary, :host),
      url_signature_key: Application.get_env(:ex_imaginary, :url_signature_key),
      token: Application.get_env(:ex_imaginary, :token)
    }
  end

  defp sign(url_signature_key, path, query) do
    mac = :crypto.mac(:hmac, :sha256, url_signature_key, path <> query)

    Base.url_encode64(mac, padding: false)
  end

  @doc """
  Generates a secure Imginary URL given:
  * `path` - The URL path of the image operation.
  * `params` - (optional) A map containing Imgaginary API parameters used to manipulate the image.
  * `config` - (optional) A map containing Imganiry source config:
      * `:host` - The Imaginary host.
      * `:token` - The token for API authorization.
      * `:url_signature_key` - The url signature key used to verify the API url signature.
  ## Examples
      iex> ExImaginary.url "/resize", %{nocrop: true, type: "jpeg", url: "https://raw.githubusercontent.com/mrdotb/i/master/mcdo.jpg"}
      "http://localhost:9000/resize?nocrop=true&type=jpeg&url=https%3A%2F%2Fraw.githubusercontent.com%2Fmrdotb%2Fi%2Fmaster%2Fmcdo.jpg"
      iex> ExImaginary.url "/thumbnail", %{url: "https://raw.githubusercontent.com/mrdotb/i/master/mcdo.jpg"}
      "http://localhost:9000/thumbnail?url=https%3A%2F%2Fraw.githubusercontent.com%2Fmrdotb%2Fi%2Fmaster%2Fmcdo.jpg"
  """
  def url(path, params \\ %{}, config \\ config()) do
    query = encode_query(params, config.token)
    url = "#{config.host}#{path}?#{query}"

    url
    |> maybe_sign_url(path, query, config.url_signature_key)
  end

  defp encode_query(params, nil), do: URI.encode_query(params)

  defp encode_query(params, token) do
    params
    |> Map.put(:token, token)
    |> URI.encode_query()
  end

  defp maybe_sign_url(url, _path, _query, nil), do: url

  defp maybe_sign_url(url, path, query, url_signature_key) do
    signature = sign(url_signature_key, path, query)

    if query == "" do
      "#{url}?sign=#{signature}"
    else
      "#{url}&sign=#{signature}"
    end
  end
end