lib/ueberauth/strategy/notion.ex

defmodule Ueberauth.Strategy.Notion do
  @moduledoc """
  Provides an Ueberauth strategy for authenticating with Notion.
  """

  use Ueberauth.Strategy,
    uid_field: :id,
    default_scope: "",
    send_redirect_uri: true,
    oauth2_module: Ueberauth.Strategy.Notion.OAuth

  alias Ueberauth.Auth.Info
  alias Ueberauth.Auth.Credentials
  alias Ueberauth.Auth.Extra

  @doc """
  Handles the initial redirect to the Notion authentication page.
  """
  def handle_request!(conn) do
    opts =
      []
      |> with_state_param(conn)
      |> with_redirect_uri(conn)

    module = option(conn, :oauth2_module)
    redirect!(conn, apply(module, :authorize_url!, [opts]))
  end

  @doc """
  Handles the callback from Notion.

  When there is a failure from Notion, the failure is included in the
  `ueberauth_failure` struct. Otherwise the information returned from Notion is
  returned in the `Ueberauth.Auth` struct.
  """
  def handle_callback!(%Plug.Conn{params: %{"code" => code}} = conn) do
    module = option(conn, :oauth2_module)
    token_response = apply(module, :get_token!, [
      [code: code, redirect_uri: callback_url(conn), grant_type: "authorization_code"]
    ])

    if token_response.access_token == nil do
      set_errors!(conn, [
        error(token_response.error, token_response.error_description)
      ])
    else
      authorization_fields = %{
        access_token: token_response.access_token,
        refresh_token: token_response.refresh_token,
        expires_at: token_response.expires_at,
        token_type: token_response.token_type
      }
      
      conn
      |> put_private(:notion_authorization, authorization_fields)
      |> put_private(:notion_other_params, Map.get(token_response, :other_params, %{}))
    end
  end

  @doc false
  def handle_callback!(conn) do
    set_errors!(conn, [error("missing_code", "No code received")])
  end

  @doc """
  Cleans up the private area of the connection used for passing the raw Notion
  response around during the callback.
  """
  def handle_cleanup!(conn) do
    conn
    |> put_private(:notion_authorization, nil)
    |> put_private(:notion_other_params, nil)
  end

  @doc """
  Includes the credentials from the Notion response.
  """
  def credentials(conn) do
    auth_data = conn.private.notion_authorization

    %Credentials{
      token: auth_data.access_token,
      refresh_token: auth_data.refresh_token,
      expires: !!auth_data.expires_at,
      expires_at: auth_data.expires_at,
      token_type: auth_data.token_type
    }
  end

  @doc """
  Fetches the fields to populate the info section of the `Ueberauth.Auth`
  struct.
  """
  def info(conn) do
    other_data = conn.private.notion_other_params

    %Info{
      name: other_data["owner"]["user"]["name"],
      email: other_data["owner"]["user"]["person"]["email"],
      image: other_data["owner"]["user"]["avatar_url"]
    }
  end

  def extra(conn) do
    other_data = conn.private.notion_other_params

    %Extra{
      raw_info: %{
        bot_id: Map.get(other_data, "bot_id", ""),
        duplicated_template_id: Map.get(other_data, "duplicated_template_id", ""),
        owner: Map.get(other_data, "owner", {}),
        workspace: %{
          id: Map.get(other_data, "workspace_id", ""),
          icon: Map.get(other_data, "workspace_icon", ""),
          name: Map.get(other_data, "workspace_name", "")
        }
      }
    }
  end

  defp fetch_uid(key, conn) do
    conn.private.notion_other_params[key]
  end

  defp option(conn, key) do
    Keyword.get(options(conn), key, Keyword.get(default_options(), key))
  end

  defp with_redirect_uri(opts, conn) do
    if option(conn, :send_redirect_uri) do
      opts |> Keyword.put(:redirect_uri, callback_url(conn))
    else
      opts
    end
  end
end