Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions lib/github_webhook.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,24 @@ defmodule GitHubWebhook do
"x-github-hook-installation-target-id" => :installation_target_id
}

defmodule CacheBodyReader do
@moduledoc false

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

@plug_parser Plug.Parsers.init(
parsers: [:json],
body_reader: {CacheBodyReader, :read_body, []},
json_decoder: Application.compile_env(:github_webhook, :json_library, Jason)
)

#

@impl true
def init(options) do
options
Expand All @@ -31,11 +49,12 @@ defmodule GitHubWebhook do
secret = get_config(options, :secret)
{module, function} = get_config(options, :action)

{:ok, payload, conn} = read_body(conn)
conn = Plug.Parsers.call(conn, @plug_parser)

[signature_in_header] = get_req_header(conn, "x-hub-signature")

if verify_signature(payload, secret, signature_in_header) do
apply(module, function, [conn, payload, request_header_opts(conn)])
if verify_signature(conn.assigns.raw_body, secret, signature_in_header) do
apply(module, function, [conn, conn.body_params, request_header_opts(conn)])
conn |> send_resp(200, "OK") |> halt()
else
conn |> send_resp(403, "Forbidden") |> halt()
Expand Down
11 changes: 5 additions & 6 deletions test/github_webhook_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ defmodule GitHubWebhookTest do
use Plug.Test

@test_body %{"hello" => "world"}
@test_body_serialized Jason.encode!(@test_body)

# Demo plug with basic auth and a simple index action
defmodule DemoPlug do
Expand Down Expand Up @@ -40,7 +39,7 @@ defmodule GitHubWebhookTest do
|> DemoPlug.call([])

assert conn.status == 200
assert Process.get(:payload) == @test_body_serialized
assert Process.get(:payload) == @test_body
assert !Process.get(:next_in_chain_called)
end

Expand All @@ -60,7 +59,7 @@ defmodule GitHubWebhookTest do
|> DemoPlug.call([])

assert conn.status == 200
assert Process.get(:payload) == @test_body_serialized
assert Process.get(:payload) == @test_body
end

test "when path does not match, skips this plug and proceeds to next one" do
Expand Down Expand Up @@ -96,7 +95,7 @@ defmodule GitHubWebhookTest do
|> DemoPlugParamPresendence.call([])

assert conn.status == 200
assert Process.get(:payload) == @test_body_serialized
assert Process.get(:payload) == @test_body
end

test "when secret is not set in params, it uses application setting" do
Expand All @@ -118,7 +117,7 @@ defmodule GitHubWebhookTest do
|> DemoPlugApplicationSecret.call([])

assert conn.status == 200
assert Process.get(:payload) == @test_body_serialized
assert Process.get(:payload) == @test_body
end

test "when secret is not set in params or Application setting, it assumes an empty secret" do
Expand All @@ -140,7 +139,7 @@ defmodule GitHubWebhookTest do
|> DemoPlugNoSecret.call([])

assert conn.status == 200
assert Process.get(:payload) == @test_body_serialized
assert Process.get(:payload) == @test_body
end

defp gh_webhook_request(body \\ %{"hello" => "world"}, secret \\ "secret") do
Expand Down