Skip to main content

docs/absinthe.md

# Absinthe Guide

PermitEx includes optional Absinthe middleware compiled only when `:absinthe`
is present in the project.

## Setup

Add `:absinthe` to your dependencies and configure PermitEx normally:

```elixir
def deps do
  [
    {:permit_ex, "~> 0.1"},
    {:absinthe, "~> 1.7"},
    {:absinthe_plug, "~> 1.5"}
  ]
end
```

```elixir
config :permit_ex, repo: MyApp.Repo
```

## Passing the Scope to Absinthe

Load the authorization scope in your context-building plug and pass it through
Absinthe's context map:

```elixir
defmodule MyAppWeb.Context do
  @behaviour Plug

  def init(opts), do: opts

  def call(conn, _opts) do
    scope = conn.assigns[:current_scope]
    Absinthe.Plug.put_options(conn, context: %{current_scope: scope})
  end
end
```

Add it to your router pipeline:

```elixir
pipeline :graphql do
  plug MyAppWeb.AuthPipeline   # sets conn.assigns.current_scope
  plug MyAppWeb.Context
  plug Absinthe.Plug, schema: MyApp.Schema
end
```

## Field-Level Middleware

Require a permission on a single field:

```elixir
middleware PermitEx.Absinthe.RequirePermission, "orders:manage"
```

Require a role:

```elixir
middleware PermitEx.Absinthe.RequireRole, "admin"
```

Use `RequireAuthorization` for richer checks:

```elixir
middleware PermitEx.Absinthe.RequireAuthorization,
  any_permissions: ["orders:manage", "settings:manage"]
```

## Full Example

```elixir
defmodule MyApp.Schema do
  use Absinthe.Schema

  mutation do
    field :create_order, :order do
      middleware PermitEx.Absinthe.RequirePermission, "orders:manage"
      arg :input, non_null(:order_input)
      resolve &MyApp.Resolvers.Orders.create/2
    end

    field :delete_order, :order do
      middleware PermitEx.Absinthe.RequireAuthorization,
        any_roles: ["admin", "support"]
      arg :id, non_null(:id)
      resolve &MyApp.Resolvers.Orders.delete/2
    end
  end

  query do
    field :orders, list_of(:order) do
      middleware PermitEx.Absinthe.RequirePermission, "orders:view"
      resolve &MyApp.Resolvers.Orders.list/2
    end
  end
end
```

## Custom Scope Key

If your context uses a different key:

```elixir
middleware PermitEx.Absinthe.RequireAuthorization,
  permission: "orders:manage",
  assign_key: :auth_scope
```

## Custom Error Message

```elixir
middleware PermitEx.Absinthe.RequireAuthorization,
  permission: "orders:manage",
  message: "You do not have permission to manage orders"
```

## Multi-Tenant SaaS

Load the workspace-scoped scope before passing it to Absinthe:

```elixir
def call(conn, _opts) do
  workspace = conn.assigns[:current_workspace]
  user = conn.assigns[:current_user]
  scope = PermitEx.Scope.for_user(user, workspace)
  Absinthe.Plug.put_options(conn, context: %{current_scope: scope})
end
```

The middleware checks apply the same context-aware role resolution as Plug and
LiveView adapters.