lib/ex_azure_key_vault/auth.ex

defmodule ExAzureKeyVault.Auth do
  @moduledoc """
  Internal module for getting authentication token for Azure connection.
  """
  alias __MODULE__
  alias ExAzureKeyVault.HTTPUtils

  @enforce_keys [:client_id, :client_secret, :tenant_id]
  defstruct(
    client_id: nil,
    client_secret: nil,
    tenant_id: nil
  )

  @type t :: %__MODULE__{
          client_id: String.t(),
          client_secret: String.t(),
          tenant_id: String.t()
        }

  @doc """
  Creates `%ExAzureKeyVault.Auth{}` struct with account tokens.

  ## Examples

      iex(1)> ExAzureKeyVault.Auth.new("6f185f82-9909...", "6f1861e4-9909...", "6f185bb8-9909...")
      %ExAzureKeyVault.Auth{
        client_id: "6f185f82-9909...",
        client_secret: "6f1861e4-9909...",
        tenant_id: "6f185bb8-9909..."
      }

  """
  @spec new(String.t(), String.t(), String.t()) :: Auth.t()
  def new(client_id, client_secret, tenant_id) do
    %Auth{client_id: client_id, client_secret: client_secret, tenant_id: tenant_id}
  end

  @doc """
  Returns bearer token for Azure connection.

  ## Examples

      iex(1)> ExAzureKeyVault.Auth.new("6f185f82-9909...", "6f1861e4-9909...", "6f185bb8-9909...")
      ...(1)> |> ExAzureKeyVault.Auth.get_bearer_token()
      {:ok, "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}

  """
  @spec get_bearer_token(Auth.t()) :: {:ok, String.t()} | {:error, any}
  def get_bearer_token(%Auth{} = params) do
    url = auth_url(params.tenant_id)
    body = auth_body(params.client_id, params.client_secret)
    headers = HTTPUtils.headers_form_urlencoded()
    options = HTTPUtils.options_ssl()

    case HTTPoison.post(url, body, headers, options) do
      {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
        response = Jason.decode!(body)
        {:ok, "Bearer #{response["access_token"]}"}

      {:ok, %HTTPoison.Response{status_code: status, body: ""}} ->
        HTTPUtils.response_client_error_or_ok(status, url)

      {:ok, %HTTPoison.Response{status_code: status, body: body}} ->
        HTTPUtils.response_client_error_or_ok(status, url, body)

      {:error, %HTTPoison.Error{reason: :nxdomain}} ->
        HTTPUtils.response_server_error(:nxdomain, url)

      {:error, %HTTPoison.Error{reason: reason}} ->
        HTTPUtils.response_server_error(reason)

      _ ->
        {:error, "Something went wrong"}
    end
  end

  @spec auth_url(String.t()) :: String.t()
  defp auth_url(tenant_id) do
    "https://login.windows.net/#{tenant_id}/oauth2/token"
  end

  @spec auth_body(String.t(), String.t()) :: tuple
  defp auth_body(client_id, client_secret) do
    {:form,
     [
       grant_type: "client_credentials",
       client_id: client_id,
       client_secret: client_secret,
       resource: "https://vault.azure.net"
     ]}
  end
end