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:
- Use the
Permit.Absinthemixin to point to the application's authorization module that configures permissions & action names, as well as pointsPermit.Ectoto the application's EctoRepo.
use Permit.Absinthe, authorization_module: MyApp.Authorization- 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 thepermitmacro option.
field :notes, list_of(non_null(:note)) do
permit action: :read
resolve &PermitAbsinthe.load_and_authorize/2
endThe 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)
endPermit.Ecto.Resolveruses the subject and authorization module to ask for permission to take the:actionby 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.
- 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
endSee more in Permit.Absinthe.Middleware documentation.
Summary
Functions
Resolves and authorizes a resource or list of resources.
Functions
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 fieldresolution- 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