SquatchMail.SNS.RawBodyReader (SquatchMail v0.1.0)

Copy Markdown View Source

A Plug.Parsers body reader that caches the raw request body in conn.assigns[:raw_body] before returning it for JSON parsing.

SquatchMail.SNS.MessageVerifier/SquatchMail.SNS.Processor.process/2 need the exact bytes SNS sent (to rebuild the signed string and, incidentally, because Processor.process/2 does its own JSON decode) - by the time Plug.Parsers hands a controller a parsed conn.params, the original body is gone unless something captured it first.

Wiring this in

Plug.Parsers accepts a :body_reader option that must be a {module, function, args} tuple implementing the same contract as Plug.Conn.read_body/2 ({:ok, body, conn} | {:more, partial, conn} | {:error, term}), invoked once per chunk. Point it at this module wherever the webhook route's pipeline configures Plug.Parsers, scoped to just the /webhooks/sns/:token path (raw-body caching in conn.assigns is a small, bounded cost - fine to scope broadly too, but the SNS route is the only one that needs it):

plug Plug.Parsers,
  parsers: [:json],
  json_decoder: Jason,
  body_reader: {SquatchMail.SNS.RawBodyReader, :read_body, []}

This must run before :json parsing consumes the body. The router or endpoint that mounts SquatchMail.Web.WebhookController needs a pipeline built this way for the webhook path (the dashboard's browser pipeline/live_session for the rest of the routes does not need this).

Summary

Functions

Drop-in replacement for Plug.Conn.read_body/2 that additionally appends every chunk read to conn.assigns[:raw_body] (starting from ""), so the full raw body is available under that key once parsing completes - including when the body arrives in multiple chunks ({:more, ...}).

Functions

read_body(conn, opts \\ [])

@spec read_body(
  Plug.Conn.t(),
  keyword()
) ::
  {:ok, binary(), Plug.Conn.t()}
  | {:more, binary(), Plug.Conn.t()}
  | {:error, term()}

Drop-in replacement for Plug.Conn.read_body/2 that additionally appends every chunk read to conn.assigns[:raw_body] (starting from ""), so the full raw body is available under that key once parsing completes - including when the body arrives in multiple chunks ({:more, ...}).