Permit.Absinthe.Resolvers.LoadAndAuthorize (permit_absinthe v0.3.0)

Copy Markdown View Source

Absinthe resolver that loads and authorizes a resource or list of resources by combining Permit's authorization rules with Ecto-based query scoping.

Usage and mechanism

In the basic scenario, Ecto is used to filter out records matching defined authorization permissions. The happy path works in the following way:

  1. Use the Permit.Absinthe mixin to point to the application's authorization module that configures permissions & action names, as well as points Permit.Ecto to the application's Ecto Repo.
use Permit.Absinthe, authorization_module: MyApp.Authorization
  1. When a GraphQL field resolves through load_and_authorize/2, the subject (resolution.context[:current_user] by default) is matched with the action configured via the permit macro option.
field :notes, list_of(non_null(:note)) do
  permit action: :read
  resolve &PermitAbsinthe.load_and_authorize/2
end

The resource module, which is typically an Ecto schema, is configured in the GraphQL type via the :schema option in the permit macro.

object :note do
  # fields

  permit(schema: Note)
end
  1. Permit.Ecto.Resolver uses the subject and authorization module to ask for permission to take the :action by the current user on objects of the resource module's struct type. If permission is granted, configured permissions are converted to Ecto query.
# This permission...
def can(user) do
  permit() |> read(Note, user_id: user.id)
end

# ...gets converted to the following query:
SELECT * FROM notes WHERE user_id: $1;

For single-object queries (e.g. note(id: 123)), the ID param is applied to the query as well.

  1. If the field is configured as a list_of(...) node, the list of retrieved records is returned by the resolver as an {:ok, [%Note{}, ...]} tuple. If it's a single-object node, the resolver function returns {:ok, %Note{}}.

Error handling

An error tuple is returned when:

  • {:error, "Not found"} - in single-object queries when the SQL query with authorization conditions returns 0 records and an additional query (with only the ID condition) indicates that a record indeed does not exist at all
  • {:error, "Unauthorized"} - when authorization fails at any point, or the additional query indicated that a record with the given ID does exist (so the query-based authorization has failed).

Additional options

See Permit.Absinthe.permit/1 macro for additional options allowing customization of queries, error handling, current user (subject) retrieval, and ID parameter & ID field naming.

Usage as middleware

For mutations, pair Permit.Absinthe.Middleware with a custom resolver. The middleware loads and authorizes the resource using the load_and_authorize/2 resolver function, then places it in the context as :loaded_resource (single) or :loaded_resources (list):

Example

mutation do
  field :update_post, :post do
    permit action: :update
    arg :id, non_null(:id)
    arg :title, :string

    middleware Permit.Absinthe.Middleware

    resolve fn _, args, %{context: %{loaded_resource: post}} ->
      MyApp.Blog.update_post(post, args)
    end
  end
end

See more in Permit.Absinthe.Middleware documentation.

Summary

Functions

Resolves and authorizes a resource or list of resources.

Functions

load_and_authorize(args, resolution)

Resolves and authorizes a resource or list of resources.

This function can be used as a resolver function directly or called from a custom resolver.

Parameters

  • args - The arguments passed to the field
  • resolution - The Absinthe resolution struct

Examples

# As a resolver function
field :post, :post do
  arg :id, non_null(:id)
  resolve &load_and_authorize/2
end

# Resolver for a list of resources
field :posts, list_of(:post) do
  resolve &load_and_authorize/2
end

# From a custom resolver
def my_custom_resolver(parent, args, resolution) do
  case load_and_authorize(parent, args, resolution, :one) do
    {:ok, resource} ->
      # Do something with the authorized resource
      {:ok, transform_resource(resource)}

    error ->
      error
  end
end