RequestSeal.Ash.Scope (RequestSeal v0.5.2)

Copy Markdown View Source

Caller-bound actor, tenant, and bounded verification context for Ash actions.

Construct with RequestSeal.Ash.scope/2. principal is a classification only; the opaque identity value stays in caller-selected actor/tenant values. Default inspection exposes only principal and authorize?.

to_opts/1 works without Ash installed. When Ash is present at compilation, the guarded Ash.Scope.ToOpts implementation also enables scope: scope. When adding Ash to a consumer that already compiled RequestSeal without it, run mix deps.compile request_seal --force to compile the protocol implementation.

Summary

Functions

Returns explicit actor, tenant, context, and authorize?: true options for Ash calls.

Types

t()

@type t() :: %RequestSeal.Ash.Scope{
  actor: term() | nil,
  authorize?: true,
  context: %{
    request_seal: %{
      label: binary(),
      profile: %{name: :rfc9421} | %{name: :web_bot_auth, revision: binary()},
      principal: :attributed | :unattributed,
      covered: [binary()],
      content: :not_required | :checked,
      freshness: :not_evaluated | :evaluated,
      replay: :not_required | :claimed
    }
  },
  principal: :attributed | :unattributed,
  tenant: term() | nil
}

Functions

to_opts(scope)

@spec to_opts(t()) :: keyword()

Returns explicit actor, tenant, context, and authorize?: true options for Ash calls.

Works without Ash installed. Pass the result as Ash action options, or pass scope: scope when Ash's scope protocol is compiled in.

Parameters

Returns

  • keyword list - [actor: actor, tenant: tenant, context: context, authorize?: true].

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> {:ok, message} = RequestSeal.Message.request("GET", "https://docs.example.com/reports/7", [], nil)
iex> {:ok, signed} =
...>   RequestSeal.sign(message, %{
...>     label: "sig",
...>     algorithm: "ed25519",
...>     components: ~s[("@method" "@path")],
...>     expires_in: 60
...>   }, handle)
iex> {:ok, policy} =
...>   RequestSeal.Policy.new(%{
...>     algorithms: ["ed25519"],
...>     components: ~s[("@method" "@path")],
...>     key_resolver: fn _ -> {:ok, %{algorithm: "ed25519", key: key}} end,
...>     freshness: :not_evaluated,
...>     content: :not_required,
...>     replay: :not_required
...>   })
iex> {:ok, verification} = RequestSeal.verify(signed, policy, label: "sig")
iex> {:ok, scope} =
...>   RequestSeal.Ash.scope(verification, %{
...>     actor: fn _principal -> :error end,
...>     unattributed: :anonymous,
...>     tenant: :none
...>   })
iex> opts = RequestSeal.Ash.Scope.to_opts(scope)
iex> Keyword.keys(opts)
[:actor, :tenant, :context, :authorize?]
iex> {opts[:actor], opts[:tenant], opts[:authorize?], opts[:context].request_seal.label}
{nil, nil, true, "sig"}
iex> RequestSeal.Custody.Local.release(handle)
:ok

Fragment (requires an Ash resource with a data layer; not executed here):

Ash.read(MyApp.Docs.Report, RequestSeal.Ash.Scope.to_opts(scope))