AbsinthePermission (AbsinthePermission v1.0.0)

Copy Markdown View Source

Declarative, schema-first authorization for Absinthe GraphQL.

Auth rules live next to the field they protect. They are compiled to introspectable data at schema-compile time, evaluated by middleware at request time, and emit telemetry on every decision.

At a glance

defmodule MyApp.Schema do
  use Absinthe.Schema
  use AbsinthePermission

  loaders do
    loader :todo, fn id, _ctx -> MyApp.Todos.get(id) end
  end

  query do
    field :todos, list_of(:todo) do
      authorize "view_todos"
      resolve &MyApp.Resolvers.list_todos/2
    end
  end

  mutation do
    field :update_todo, :todo do
      arg :id, :integer
      arg :state, :string

      authorize "edit_todos"
      authorize "close_todos", when: arg(:state) == "CLOSED"

      authorize_owner :todo,
        by: arg(:id),
        if_owner: "edit_own_todo",
        if_other: "edit_others_todo"

      resolve &MyApp.Resolvers.update_todo/2
    end
  end
end

Required Absinthe context

At request time the middleware expects two keys in the Absinthe context:

  • :current_user — any term; available as current_user / current_user(:field) in conditions
  • :permissions — a list of permission strings the caller holds

Set them in your Plug pipeline before Absinthe runs:

conn
|> Absinthe.Plug.put_options(
  context: %{
    current_user: user,
    permissions: MyApp.Auth.permissions_for(user)
  }
)

If the context is missing these keys, the middleware raises AbsinthePermission.MissingContextError by default. Override with the :on_missing_context option to use AbsinthePermission:

use AbsinthePermission, on_missing_context: :deny  # or :allow

Options for use AbsinthePermission

  • :on_missing_context:raise (default), :deny, or :allow. Behaviour when current_user/permissions are absent from context.

See also

Summary

Functions

Sets up a schema module to use AbsinthePermission.

Return all rules in a schema, grouped by {type_id, field_id}.

Look up a registered loader function.

Return all loads declared for a field on a schema.

Return the list of rules attached to a field on a schema.

Functions

__using__(opts \\ [])

(macro)

Sets up a schema module to use AbsinthePermission.

Imports the DSL, registers the necessary module attributes, hooks into Absinthe's middleware pipeline, and installs a @before_compile callback that generates the rule lookup functions.

Options

  • :on_missing_context:raise | :deny | :allow (default :raise)

all_rules(schema)

@spec all_rules(module()) :: %{
  required({atom(), atom()}) => [AbsinthePermission.Rule.t()]
}

Return all rules in a schema, grouped by {type_id, field_id}.

This is what mix absinthe_permission.audit consumes.

loader(schema, name)

@spec loader(module(), atom()) :: (any(), map() -> any()) | nil

Look up a registered loader function.

AbsinthePermission.loader(MyApp.Schema, :todo)
#=> #Function<...>

loads_for(schema, type_id, field_id)

@spec loads_for(module(), atom(), atom()) :: [AbsinthePermission.Load.t()]

Return all loads declared for a field on a schema.

AbsinthePermission.loads_for(MyApp.Schema, :mutation, :update_todo)

rules_for(schema, type_id, field_id)

@spec rules_for(module(), atom(), atom()) :: [AbsinthePermission.Rule.t()]

Return the list of rules attached to a field on a schema.

AbsinthePermission.rules_for(MyApp.Schema, :mutation, :update_todo)

Returns [] when the field has no rules. Useful in tests, audits, and for AI agents inspecting the schema.