node field resolution errors when schema-level middleware puts an error result in
dpehrson opened this issue · 3 comments
I was attempting to apply a schema-wide middleware that would restrict access to all mutations/query fields by default, white-listing only certain ones to non-authenticated/administrative users.
The middleware in question looks like this:
defmodule MyApp.Graph.Private.Middleware.AdminEnforcement do
@behaviour Absinthe.Middleware
def call(resolution, _config) do
case resolution.context do
%{current_user: %MyApp.User{is_admin: true}} -> resolution
_ -> Absinthe.Resolution.put_result(resolution, {:error, "Internal error"})
end
end
end
I attempted to configure the middleware at the schema level to only allow non-admins to do the following:
- Query only this portion of the graph:
query { viewer { isAuthenticated, isAuthorized } }
which would allow a client to check only whether they were logged in, and if so, whether they were in any way authorized to use this graph - Perform only the mutations necessary to authenticate/deauthenticate via
mutation { authenticate }
andmutation { relinquishToken }
The following middleware/3
definition was put in place at the schema level with help from @benwilson512
def middleware(middleware, field, object) do
middleware
|> apply_middleware(:admin_enforcement, field, object)
end
# Restrict all root query fields except `viewer` to administrators
defp apply_middleware(middleware, :admin_enforcement, %{identifier: identifier}, %{identifier: :query})
when not identifier in [:viewer]
do
[Middleware.AdminEnforcement | middleware]
end
# Restrict all viewer fields except `isAuthenticated` and `isAuthorized` to administrators
defp apply_middleware(middleware, :admin_enforcement, %{identifier: identifier}, %{identifier: :viewer})
when not identifier in [:is_authorized, :is_authenticated]
do
[Middleware.AdminEnforcement | middleware]
end
# Restrict all mutations except `authenticate` and `relinquishToken` to administrators
defp apply_middleware(middleware, :admin_enforcement, %{identifier: identifier}, %{identifier: :mutation})
when not identifier in [:authenticate, :relinquish_token]
do
[Middleware.AdminEnforcement | middleware]
end
defp apply_middleware(middleware, _name, _field, _object), do: middleware
However, when issuing the following query:
query { node(id: "...") { id }
The following error is thrown:
** (FunctionClauseError) no function clause matching in Absinthe.Relay.Node.resolve_with_global_id/2
(absinthe_relay) lib/absinthe/relay/node.ex:116: Absinthe.Relay.Node.resolve_with_global_id(#Absinthe.Resolution<...>)
To work around this, I was able to exempt node
from the enforcement at the schema-level just like viewer
and then apply the same middleware manually within the node field
definition in the query root definition.
For some reason applying this middleware at the field-level works, but applying it at the schema-level does not. After talking with @benwilson512 the assumption is that somewhere in absinthe_relay's node resolution code, it is not checking to see if resolution has been halted.
I hope this is enough to understand the potential issue, let me know if I can answer any other questions.
This morning I was able to reduce this to it's essence. The following is the minimum schema required to trigger this bug:
defmodule Schema do
defmodule BadMiddleware do
@behaviour Absinthe.Middleware
def call(resolution, _config) do
Absinthe.Resolution.put_result(resolution, {:error, "Internal error"})
end
end
defmodule Root do
defstruct id: "root"
end
use Absinthe.Schema
use Absinthe.Relay.Schema
def middleware(middleware, field, object) do
[BadMiddleware | middleware]
end
node interface do
resolve_type fn
%Root{}, _ -> :root
_, _ -> nil
end
end
node object :root do
end
query do
node field do
resolve fn
%{type: :root}, _info -> {:ok, %Root{}}
end
end
end
end
Issuing the following query against that schema will result in the error:
query {
node(id: "Um9vdDpyb290") {
id
}
}
Simply removing the middleware will allow the schema to behave properly.
Thanks! I'll take a look at it as soon as I can.
@benwilson512 np, I'll be gone until Tuesday for a family thing, but if you haven't solved it by then I'll try to get a PR up with at least a failing test and ideally a solution.