# Mastodon Client

WARNING

This part of the documentation is based on few tests have made. Please be careful if you want to use that in production.

# Resources and References

  • https://docs.joinmastodon.org/api/
  • https://github.com/scrogson/oauth2

# Gihub Webhook

WARNING

This documentation was not totally tested and is only based on a debugging session.

The first part of the webhook is pretty straighforward. You can create a new controller and define what kind of action the webhook will do.

defmodule MyApp.WebhookController do
  use FriendlyHookWeb, :controller
  require Logger

  def valid_signature(value, body) do
    secret = Application.fetch_env!(:my_app, MyApp.Endpoint)
    |> Keyword.get(:github)
    |> Keyword.get(:secret)
    hashed_secret = :crypto.hmac(:sha256, secret, body)
    |> Base.encode16
    |> String.downcase
    final = "sha256=" <> hashed_secret
    final == value
  end

  def index(conn, param) do
    [github_signature|_] = get_req_header(conn, "x-hub-signature-256")
    body = conn.assigns[:raw_body]
    Logger.debug(body)
    case valid_signature(github_signature, body) do
      true -> valid_connection(conn, param)
      false -> conn |> put_status(403) |> json(%{})
    end
  end

  defp valid_connection(conn, param) do
    json(conn, %{ :status => :ok })
  end
end

The second part is to validate the signature. To do that, your application and Phoenix in particular will require to have access to the raw body part of the payload. Unfortunately, by default, this value is not available in the conn parameter.

lib/endpoint.ex is containing all information about the default Plug but also many other configuration. The interesting part here is Plug.Parsers, this module will read the raw body and convert it in the desired format. If you follow the documentation (opens new window) about this solution, it will not give you where to add this behavior. So, here the original Plug:

plug Plug.Parsers,
  parsers: [:urlencoded, :multipart, :json],
  pass: ["*/*"],
  json_decoder: Phoenix.json_library()

Now, this is the new one, crafted to use CacheBodyReader module to store the raw body directly in the connection.

plug Plug.Parsers,
  parsers: [:urlencoded, :multipart, :json],
  pass: ["*/*"],
  body_reader: {CacheBodyReader, :read_body, []},
  json_decoder: Jason

You can create the CacheBodyReader anywhere you want, I created it in lib/cache_body_reader.ex:

defmodule CacheBodyReader do
  def read_body(conn, opts) do
    {:ok, body, conn} = Plug.Conn.read_body(conn, opts)
    conn = update_in(conn.assigns[:raw_body], &[body | (&1 || [])])
    {:ok, body, conn}
  end
end

So, now, you will have access also to the raw body by using this function:

raw_body = conn.assigns[:raw_body]

Reload your application and you have now a secure way to deal with Github webhooks.

# References and Resources