# ueberauth_mastodon

Log into [Mastodon]( and [Pleroma]( with [Überauth](

This library makes it easy to spin up Elixir microservices to run alongside your social media website.

You can configure one or more Mastodon/Pleroma servers as login options.

## Usage guide

### Configuration

# Tesla
config :tesla, adapter: Tesla.Adapter.Hackney

# Ueberauth
config :ueberauth, Ueberauth,
  providers: [
    # You will create routes matching the provider name:
    # - /auth/mastodon
    # - /auth/mastodon/callback
    mastodon: {Ueberauth.Strategy.Mastodon, [
      # instance: "https://example.tld",
      # client_id: "********",
      # client_secret: "********"

    # This one will be at /auth/gleasonator
         # You MUST provide an instance.
         instance: "",
         # You MUST provide app credentials.
         # Generate your app before getting started.
         client_id: "3WCR-5e3nOg2SJ90W134VLIIwmib2T96qsXWSJAAEUs",
         client_secret: "r-vCWcOk_7IY202yYMMgEHEVEtd5Gv4tlByZqVChRm0"

#### Tesla

Under the hood, ueberauth_mastodon uses [Tesla]( to make HTTP requests.

Tesla is not an HTTP client itself, but a flexible layer for switchable HTTP clients.
In this guide we use Hackney, but you can use whatever you want.
Just don't leave it blank.

#### Options

- `instance` (required) - A URL to the Mastodon/Pleroma instance.
- `client_id` (required) - Generated by an app. Create the app first.
- `client_secret` (required) - Generated by an app. Create the app first.
- `redirect_uri` - Override the redirect URL. By default it goes to `/auth/:provider/callback`
- `scope` - Space-separated list of scopes, eg `read write follow`. It defaults to `read`.
- `uid_field` - Which field from Mastodon API to map to Überauth. It's set to `"url"` (the ActivityPub ID) by default.

### Routes

You'll need to create matching routes in `router.ex`:

scope "/auth", PatronWeb do
  pipe_through [:browser, Ueberauth]

  get "/mastodon", AuthController, :request
  get "/mastodon/callback", AuthController, :callback

  # You don't have to have more than one, but you can have any number
  get "/gleasonator", AuthController, :request
  get "/gleasonator/callback", AuthController, :callback

The `Ueberauth` plug will match names from your config and intercept the conn before it arrives at your controller.

You **do not** need to implement the `:request` view in your controller. Just put a link to `/auth/:provider` somewhere on your website, and it will redirect to the OAuth signup page.

You **must** provide a `:callback` view.

### Controller

You'll need to create a controller to handle the callback.
The OAuth form will redirect here.

defmodule PatronWeb.AuthController do
  use PatronWeb, :controller

  alias Ueberauth.Auth
  alias Ueberauth.Auth.Credentials
  alias Ueberauth.Failure
  alias Ueberauth.Failure.Error

  # /auth/:provider/callback
  # After the user authorizes the OAuth form, they'll be redirected back here.
  def callback(
        # An `:ueberauth_auth` key is provided upon success.
        # It contains a `%Ueberauth.Auth{}` struct.
        %{assigns: %{ueberauth_auth: %Auth{uid: uid, credentials: %Credentials{} = credentials}}} = conn,
      ) do
    # Store the credentials in a cookie, or anywhere else
    |> put_session(:token_data, credentials)
    |> put_session(:uid, uid)
    |> redirect(to: "/")

  def callback(
        # Upon failure, you'll get `:ueberauth_failure`.
        # It contains a `%Ueberauth.Failure{}` struct.
        %{assigns: %{ueberauth_failure: %Failure{errors: [%Error{message: message} | _]}}} = conn,
      ) do
    |> put_flash(:error, message)
    |> redirect(to: "/")

  # If neither exist, just redirect home
  def callback(conn, _params) do
    redirect(conn, to: "/")

### Authentication Plug

Finally, you'll likely want to create a plug to authenticate the user on pageload.
This is one possible way:

defmodule PatronWeb.Plugs.BootstrapUser do
  import Plug.Conn
  alias Patron.User
  alias Ueberauth.Auth.Credentials
  alias Ueberauth.Strategy.Mastodon

  @behaviour Plug

  def init(_), do: nil

  def call(conn, _) do
    # Get the token set from the callback
    case get_session(conn, :token_data) do
      nil -> conn
      # Make an HTTP request
      %Credentials{token: token} -> verify_token(conn, token)
      # Delete invalid token
      _ -> delete_session(conn, :token_data)

  # Fetch the account from the token
  defp verify_token(conn, token) do
    # The uid is an ActivityPub ID, which serves as a convenient base URL
    with %Credentials{uid: ap_id} <- get_session(conn, :token_data),
         {:ok, %{status: 200, body: %{"url" => ap_id} = data}} <-
           Mastodon.API.account_verify_credentials(ap_id, token) do
      |> assign(:user_data, data)
      _ -> delete_session(conn, :token_data)

And in the router:

pipeline :browser do
  # ...
  plug PatronWeb.Plugs.BootstrapUser

## Installation

Add `ueberauth`, `ueberauth_mastodon`, and a Tesla adapter to your list of dependencies in `mix.exs`:

def deps do
    {:ueberauth, "~> 0.7.0"},
    {:ueberauth_mastodon, "~> 0.1.0"},

    # For `Tesla.Adapter.Hackney` to work
    {:hackney, "~> 1.18"}

# License

ueberauth_mastodon is licensed under the MIT license.
See for the full text.