lib/ueberauth/strategy/hatena/oauth.ex

defmodule Ueberauth.Strategy.Hatena.OAuth do
  @moduledoc """
  OAuth 1.0a for Hatena.

  Add `consumer_key` and `consumer_secret` to your configuration:

  config :ueberauth, Ueberauth.Strategy.Hatena.OAuth,
    consumer_key: System.get_env("HATENA_CONSUMER_KEY"),
    consumer_secret: System.get_env("HATENA_CONSUMER_SECRET"),
    scope: "read_public,write_public"
  """

  @request_token_url "https://www.hatena.com/oauth/initiate"
  @access_token_url "https://www.hatena.com/oauth/token"
  @authorize_url "https://www.hatena.ne.jp/oauth/authorize"

  def request_token(params \\ [], opts \\ []) do
    client = client(opts)
    params = [{"oauth_callback", client.redirect_uri} | params]
    body = %{scope: client.scope}

    {consumer_key, consumer_secret, _} = client |> consumer()
    creds = OAuther.credentials(
      consumer_key: consumer_key,
      consumer_secret: consumer_secret
    )

    # Hatena does not accept body params as scope.
    # We have to set scope in url query parameters.
    url = "#{@request_token_url}?#{URI.encode_query(body)}"

    {header, _params} =
      "post"
      |> OAuther.sign(url, params, creds)
      |> OAuther.header

    HTTPoison.post(url, URI.encode_query(body), [header])
    |> decode_response
  end

  def request_token!(params \\ [], opts \\ []) do
    case request_token(params, opts) do
      {:ok, token} -> token
      {:error, error} -> raise error
    end
  end

  def authorize_url!({token, _token_secret}, callback_url) do
    "#{@authorize_url}?oauth_token=#{token}&oauth_callback=#{callback_url}"
  end

  def access_token({token, token_secret}, verifier) do
    {consumer_key, consumer_secret, _} = client() |> consumer()
    creds = OAuther.credentials(
      consumer_key: consumer_key,
      consumer_secret: consumer_secret,
      token: token,
      token_secret: token_secret
    )
    {header, _params} =
      "post"
      |> OAuther.sign(@access_token_url, [{"oauth_verifier", verifier}], creds)
      |> OAuther.header

    HTTPoison.post(@access_token_url, [], [header])
    |> decode_response
  end

  def access_token!(access_token, verifier) do
    case access_token(access_token, verifier) do
      {:ok, token} -> token
      {:error, error} -> raise error
    end
  end

  def client(opts \\ []) do
    config = Application.get_env(:ueberauth, __MODULE__)

    []
    |> Keyword.merge(config)
    |> Keyword.merge(opts)
    |> Enum.into(%{})
  end

  def consumer(client), do: {client.consumer_key, client.consumer_secret, :hmac_sha1}

  defp decode_response({:ok, %{status_code: 200, body: body, headers: _}}) do
    params = params_decode(body)
    {:ok, params}
  end
  defp decode_response({:ok, %{status_code: status_code, body: body, headers: _}}) do
    {:error, "#{status_code}: #{body}"}
  end
  defp decode_response({:error, %{reason: reason}}) do
    {:error, "#{reason}"}
  end
  defp decode_response(error) do
    {:error, error}
  end

  def params_decode(resp) do
    resp
    |> String.split("&", trim: true)
    |> Enum.map(&String.split(&1, "="))
    |> Enum.map(&List.to_tuple/1)
    |> Enum.into(%{})
  end
end