plug_webhook v0.1.0 PlugWebhook behaviour View Source

Simple tool for building plugs that handle wehbooks and verify signature.

As a design decision, this library, similar to Plug itself, does not store the request body for a long time - the whole body is stored only for the signature verification and parsing.

Example

This is how a simple webhook router that verifies signature for GithHub webhooks could look like:

defmodule MyWebhookHandler do
  @behaviour Plug
  @behaviour PlugWebhook

  def init(opts), do: opts

  def verify_signature(conn, body, _opts) do
    token = ## get github token, e.g. System.get_env("GITHUB_TOKEN")
    signature = "sha1=" <> Base.encode16(:crypto.hmac(:sha, token, body))
    [verify_against] = get_req_header(conn, "x-hub-signature")
    if Plug.Crypto.secure_compare(signature, verify_against) do
      conn
    else
      conn
      |> send_resp(400, "bad signature")
      |> halt() # remeber to always halt if signature doesn't match
    end
  end

  def call(conn, _opts) do
    [event_type] = get_req_header(conn, "x-github-event")
    handle(event_type, conn, conn.body_params)
  end

  defp handle("issues", conn, %{"action" => "opened"} = payload) do
    # handle opened issue
    send_resp(conn, 200, "ok")
  end

  defp handle(_, conn, _) do
    # pass through other events
    send_resp(conn, 200, "ok")
  end
end

With such definition, we can add this handler into our application using:

plug PlugWebhook, handler: MyWebhookHandler, parser_opts: ...

Where :parser_opts would be the options, you’d usually pass to Plug.Parsers. It’s important to add the PlugWebhook before parsers themselves. For example, in a Phoenix application, this could look as (in the endpoint module):

parser_opts = [
  parsers: [:urlencoded, :multipart, :json],
  pass: ["*/*"],
  json_decoder: Phoenix.json_library()
]

plug PlugWebhook,
  at: "/github_webhook",
  parser_opts: parser_opts,
  handler: MyWebhookHandler

plug Plug.Parsers, parser_opts

Link to this section Summary

Callbacks

Callback responsible for verifying the request signature

Link to this section Types

Link to this section Callbacks

Link to this callback verify_signature(conn, body, opts) View Source
verify_signature(conn :: Plug.Conn.t(), body :: String.t(), opts()) ::
  Plug.Conn.t()

Callback responsible for verifying the request signature.

It should behave as a plug in every respect, in particular, it must return an optionally modified conn struct.