lib/assent/strategies/vk.ex

defmodule Assent.Strategy.VK do
  @moduledoc """
  VK.com OAuth 2.0 strategy.

  The VK token endpoint does not provide data on email verification, email is
  considered unverified.

  ## Configuration

  - `:user_url_params` - Parameters to send along with the user fetch request,
    optional, defaults to `[]`

  See `Assent.Strategy.OAuth2` for more.

  ## Usage

      config = [
        client_id: "REPLACE_WITH_CLIENT_ID",
        client_secret: "REPLACE_WITH_CLIENT_SECRET",
        redirect_uri: "http://localhost:4000/auth/callback"
      ]
  """
  use Assent.Strategy.OAuth2.Base

  alias Assent.{Config, Strategy.OAuth2}

  @profile_fields ["uid", "first_name", "last_name", "photo_200", "screen_name"]
  @url_params [fields: Enum.join(@profile_fields, ","), v: "5.69", https: "1"]

  @impl true
  def default_config(config) do
    params = Config.get(config, :user_url_params, [])
    user_url_params = Config.merge(@url_params, params)

    [
      base_url: "https://api.vk.com",
      authorize_url: "https://oauth.vk.com/authorize",
      token_url: "https://oauth.vk.com/access_token",
      user_url: "/method/users.get",
      authorization_params: [scope: "email"],
      user_url_params: user_url_params,
      auth_method: :client_secret_post
    ]
  end

  @impl true
  def normalize(_config, user) do
    {:ok,
     %{
       "sub" => user["id"],
       "given_name" => user["first_name"],
       "family_name" => user["last_name"],
       "picture" => user["photo_200"],
       "email" => user["email"]
     }}
  end

  @impl true
  def fetch_user(config, token) do
    params =
      config
      |> Config.get(:user_url_params, [])
      |> Config.put(:access_token, token["access_token"])

    config
    |> OAuth2.fetch_user(token, params)
    |> handle_user_response(token)
  end

  defp handle_user_response({:ok, %{"response" => [user]}}, token) do
    user =
      user
      |> Map.put_new("id", token["user_id"])
      |> Map.put_new("email", token["email"])

    {:ok, user}
  end

  defp handle_user_response({:ok, user}, _token) do
    {
      :error,
      RuntimeError.exception("""
      Retrieved an invalid response fetching VK user.

      User response:
      #{inspect(user)}
      """)
    }
  end

  defp handle_user_response({:error, error}, _token) do
    {:error, error}
  end
end