I want to filter (not prevent access to) list fields returned from my GraphQL API based on the view method in the model policy.
I've written the directive below, which I've added to fields in my schema.
I have two questions:
This isn't the nicest way to achieve the result I want, because I have to annotate every field in my schema that fetches a relationship. Is there a better way to do this?
The solution I have below has an edge case which doesn't work. When returning a filtered Collection, it doesn't load the nested fields on the object that were requested in the schema. Ideally this would behave recursively, using the same resolver for all items in the Collection, and all of their child fields. How can I make this work?
class ViewPolicyFilterDirective extends BaseDirective implements FieldMiddleware {
/**
* Filters GraphQL list fields using Policies 'view' method
*
* @param \Nuwave\Lighthouse\Schema\Values\FieldValue $fieldValue
* @param \Closure $next
* @return \Nuwave\Lighthouse\Schema\Values\FieldValue
*/
public function handleField(FieldValue $fieldValue, Closure $next): FieldValue {
// Retrieve the existing resolver function
/** @var Closure $previousResolver */
$previousResolver = $fieldValue->getResolver();
// Wrap around the resolver
$wrappedResolver = function ($root, array $args, GraphQLContext $context, ResolveInfo $info) use ($previousResolver) {
// Call the resolver, passing along the resolver arguments
$result = $previousResolver($root, $args, $context, $info);
if ($result instanceof Collection && $result->count() > 0 && $result[0] instanceof Model) {
return $result->filter(function (Model $r) {
return Auth::user()->can('view', $r);
})->toArray();
} else if ($result instanceof Model && Auth::user()->can('view', $result)) {
return $result;
} else if ($result instanceof Model && !Auth::user()->can('view', $result)) {
throw new AuthorizationException(
"You are not authorized to access this"
);
}
return $result;
};
// Place the wrapped resolver back upon the FieldValue
// It is not resolved right now - we just prepare it
$fieldValue->setResolver($wrappedResolver);
// Keep the middleware chain going
return $next($fieldValue);
}
}