A cross-cutting rule that runs for commands and queries, whatever their own declarations say.
authorize protects one artifact. A filter protects many: a whole namespace, or everything that
matches a rule. Register filters on the application; they run in order, after the artifact's own
authorization and before validation or the query:
config :my_app, MyApp.Arc, filters: [MyApp.SecuredNamespace]
defmodule MyApp.SecuredNamespace do
@behaviour Arc.Filter
@impl true
def on_command(ctx, %Arc.Descriptor{namespace: ["MyApp", "Secured" | _]}),
do: require_role(ctx)
def on_command(_ctx, _descriptor), do: :ok
@impl true
def on_query(ctx, %Arc.Descriptor{namespace: ["MyApp", "Secured" | _]}, _query),
do: require_role(ctx)
def on_query(_ctx, _descriptor, _query), do: :ok
defp require_role(ctx) do
if "Secured" in ctx.principal.roles,
do: :ok,
else: {:unauthorized, "Role 'Secured' is required."}
end
endBoth callbacks are optional. A filter answers
:ok- carry on{:unauthorized, reason}- the caller may not: the result is not authorized (HTTP 403) and carries the reason. For an observable query the subscription ends withUnauthorized{:error, results}- reject with validation results
A filter sees the descriptor of the artifact (Arc.Descriptor, with module, name and
namespace) and the context, not the payload.
Summary
Types
Callbacks
@callback on_command(Arc.Context.t(), Arc.Descriptor.t()) :: outcome()
@callback on_query(Arc.Context.t(), Arc.Descriptor.t(), Arc.Descriptor.Query.t()) :: outcome()