# `RequestSeal.AshHooks`
[🔗](https://github.com/baselabs/request_seal/blob/v0.5.2/lib/request_seal/ash_hooks.ex#L2)

Optional ash_hooks inbound RFC 9421 verification bridge (Elixir 1.20).

A consumer's `c:AshHooks.Provider.verify_signature/3` delegates to
`verify_signature/4` with an explicit policy and label. RequestSeal never
interprets the provider's secret/key reference as key material or
authorizes an application action.

The provider's headers map has already lost duplicate lines and ordering;
the helper cannot recover them and returns only the provider's `:ok`/error
contract.
Prefer `RequestSeal.Plug.Capture` and `RequestSeal.Plug.Verify` before
ash_hooks ingress to retain ordered headers, exact body bytes, and facts.
The RequestSeal reader replays unchanged bytes to `AshHooks.BodyReader`.
Provider errors never expose key references, body bytes, or callback errors.

# `verify_signature`

```elixir
@spec verify_signature(
  binary(),
  AshHooks.Provider.verify_context(),
  binary(),
  keyword()
) ::
  :ok | {:error, :invalid_signature | :no_webhook_secret}
```

Verifies a provider's exact body under an explicit policy and signature label.

The request is rebuilt with `RequestSeal.Message.request/5` from the
context's method, URI, and header map, with `body` as retained bytes, then
checked with `RequestSeal.verify/3`.

## Parameters

  * `body` - the exact raw request body bytes.
  * `context` - an `t:AshHooks.Provider.verify_context/0` map. Its `:method`,
    absolute `:request_uri`, `:headers` map, and `:tenant` are used; other
    keys are ignored.
  * `reference` - the provider's nonempty binary secret/key reference. It is
    passed only to a policy function, never used as key material.
  * `opts` - keyword list of the options below. Unknown or duplicate
    options return `{:error, :invalid_signature}`.

## Options

  * `:policy` - a `RequestSeal.Policy`, or an arity-two function called as
    `fun.(context.tenant, reference)` that returns one.
  * `:label` - the signature label to verify.

## Returns

  * `:ok` - the signature verified. This grants no authorization.
  * `{:error, :no_webhook_secret}` - `reference` is not a nonempty binary.
  * `{:error, :invalid_signature}` - any other failure: invalid options, a
    policy function that raised, a context the message cannot be built
    from, or a verification rejection. No key reference, body bytes, or
    callback error is exposed.

## Examples

    iex> {_public, seed} = :crypto.generate_key(:eddsa, :ed25519)
    iex> {:ok, handle} = RequestSeal.Custody.Local.new("ed25519", {:ed25519, seed})
    iex> {:ok, key} = RequestSeal.Custody.public_key(handle)
    iex> body = ~s({"build":"1842","state":"passed"})
    iex> url = "https://hooks.example.com/builds"
    iex> spec = %{
    ...>   label: "sig",
    ...>   algorithm: "ed25519",
    ...>   components: ~s[("@method" "@authority" "@path" "content-digest" "content-type")],
    ...>   expires_in: 300,
    ...>   digest: ["sha-256"]
    ...> }
    iex> {:ok, message} =
    ...>   RequestSeal.Message.request("POST", url, [{"content-type", "application/json"}], body)
    iex> {:ok, signed} = RequestSeal.sign(message, spec, handle)
    iex> headers = Map.new(signed.fields, &{String.downcase(&1.name), &1.value})
    iex> context = %{
    ...>   method: "POST",
    ...>   request_uri: url,
    ...>   headers: headers,
    ...>   signature: headers["signature"],
    ...>   tenant: nil
    ...> }
    iex> {:ok, policy} =
    ...>   RequestSeal.Policy.new(%{
    ...>     algorithms: ["ed25519"],
    ...>     components: spec.components,
    ...>     key_resolver: fn _ -> {:ok, %{algorithm: "ed25519", key: key}} end,
    ...>     freshness: :not_evaluated,
    ...>     content: %{kind: :content, algorithms: ["sha-256"], section: :headers},
    ...>     replay: :not_required
    ...>   })
    iex> opts = [policy: fn nil, "builds-key" -> policy end, label: "sig"]
    iex> RequestSeal.AshHooks.verify_signature(body, context, "builds-key", opts)
    :ok
    iex> RequestSeal.AshHooks.verify_signature(body <> " ", context, "builds-key", opts)
    {:error, :invalid_signature}
    iex> RequestSeal.AshHooks.verify_signature(body, context, "", opts)
    {:error, :no_webhook_secret}
    iex> RequestSeal.Custody.Local.release(handle)
    :ok

---

*Consult [api-reference.md](api-reference.md) for complete listing*
