Last active
July 26, 2023 08:39
-
-
Save tasinttttttt/82e10af25e265bf797e2cb3deca9a36e to your computer and use it in GitHub Desktop.
Lighthouse 6 validation is processed before guarding Fix
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// app/Providers/AppServiceProvider.php | |
namespace App\Providers; | |
use Illuminate\Support\ServiceProvider; | |
class AppServiceProvider extends ServiceProvider | |
{ | |
/** | |
* Register any application services. | |
* | |
* @return void | |
*/ | |
public function register() | |
{ | |
// Register the custom graphql context | |
$this->app->bind( | |
\Nuwave\Lighthouse\Support\Contracts\CreatesContext::class, | |
\App\GraphQL\Context\ContextFactory::class | |
); | |
} | |
/** | |
* Bootstrap any application services. | |
* | |
* @return void | |
*/ | |
public function boot() | |
{ | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// app/GraphQL/Directives/CanDirective.php | |
declare(strict_types=1); | |
namespace App\GraphQL\Directives; | |
use Illuminate\Contracts\Pagination\Paginator; | |
use Illuminate\Database\Eloquent\Model; | |
use Nuwave\Lighthouse\Auth\CanDirective as LighthouseCanDirective; | |
use Nuwave\Lighthouse\Execution\Resolved; | |
use Nuwave\Lighthouse\Execution\ResolveInfo; | |
use Nuwave\Lighthouse\Schema\Values\FieldValue; | |
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext; | |
use Nuwave\Lighthouse\Support\Utils; | |
class CanDirective extends LighthouseCanDirective | |
{ | |
public function handleField(FieldValue $fieldValue): void | |
{ | |
$ability = $this->directiveArgValue('ability'); | |
$resolved = $this->directiveArgValue('resolved'); | |
$fieldValue->wrapResolver(fn (callable $resolver): \Closure => function (mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) use ($resolver, $ability, $resolved) { | |
$gate = $this->gate->forUser($context->user()); | |
$checkArguments = $this->buildCheckArguments($args); | |
if ($resolved) { | |
return Resolved::handle( | |
$resolver($root, $args, $context, $resolveInfo), | |
function ($modelLike) use ($gate, $ability, $checkArguments) { | |
$modelOrModels = $modelLike instanceof Paginator | |
? $modelLike->items() | |
: $modelLike; | |
Utils::applyEach(function (?Model $model) use ($gate, $ability, $checkArguments): void { | |
$this->authorize($gate, $ability, $model, $checkArguments); | |
}, $modelOrModels); | |
return $modelLike; | |
}, | |
); | |
} | |
// do the checks | |
foreach ($this->modelsToCheck($root, $args, $context, $resolveInfo) as $model) { | |
$this->authorize($gate, $ability, $model, $checkArguments); | |
} | |
// then throw validation error if exists in the context | |
if ($context->validationError()) { | |
throw $context->validationError(); | |
} | |
return $resolver($root, $args, $context, $resolveInfo); | |
}); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// app/GraphQL/Context/Context.php | |
namespace App\GraphQL\Context; | |
use Illuminate\Contracts\Auth\Authenticatable; | |
use Illuminate\Http\Request; | |
use Nuwave\Lighthouse\Auth\AuthServiceProvider; | |
use Nuwave\Lighthouse\Exceptions\ValidationException; | |
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext; | |
final class Context implements GraphQLContext | |
{ | |
/** First validation exception */ | |
public ?ValidationException $validationError = null; | |
/** An instance of the currently authenticated user. */ | |
public ?Authenticatable $user = null; | |
public function __construct( | |
/** | |
* An instance of the incoming HTTP request. | |
*/ | |
public Request $request, | |
) { | |
foreach (AuthServiceProvider::guards() as $guard) { | |
$this->user = $request->user($guard); | |
if (isset($this->user)) { | |
break; | |
} | |
} | |
} | |
public function request(): ?Request | |
{ | |
return $this->request; | |
} | |
public function user(): ?Authenticatable | |
{ | |
return $this->user; | |
} | |
public function setUser(?Authenticatable $user): void | |
{ | |
$this->user = $user; | |
} | |
public function validationError(): ?ValidationException | |
{ | |
return $this->validationError; | |
} | |
public function setValidationError(?ValidationException $validationError): void | |
{ | |
$this->validationError = $validationError; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// app/GraphQL/Context/ContextFactory.php | |
declare(strict_types=1); | |
namespace App\GraphQL\Context; | |
use Illuminate\Http\Request; | |
use Nuwave\Lighthouse\Support\Contracts\CreatesContext; | |
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext; | |
final class ContextFactory implements CreatesContext | |
{ | |
public function generate(?Request $request): GraphQLContext | |
{ | |
return new Context($request); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// app/GraphQL/Directives/GuardDirective.php | |
declare(strict_types=1); | |
namespace App\GraphQL\Directives; | |
use Nuwave\Lighthouse\Auth\AuthServiceProvider; | |
use Nuwave\Lighthouse\Auth\GuardDirective as LighthouseGuardDirective; | |
use Nuwave\Lighthouse\Execution\ResolveInfo; | |
use Nuwave\Lighthouse\Schema\Values\FieldValue; | |
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext; | |
class GuardDirective extends LighthouseGuardDirective | |
{ | |
public function handleField(FieldValue $fieldValue): void | |
{ | |
$fieldValue->wrapResolver(fn (callable $resolver): \Closure => function (mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) use ($resolver) { | |
$guards = $this->directiveArgValue('with', AuthServiceProvider::guards()); | |
// do the checks | |
$context->setUser($this->authenticate($guards)); | |
// then throw validation error if exists in the context | |
if ($context->validationError()) { | |
throw $context->validationError(); | |
} | |
return $resolver($root, $args, $context, $resolveInfo); | |
}); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// config/lighthouse.php | |
... | |
'namespaces' => [ | |
'directives' => ['App\\GraphQL\\Directives'], // important! | |
], | |
... | |
'field_middleware' => [ | |
\Nuwave\Lighthouse\Schema\Directives\TrimDirective::class, | |
\Nuwave\Lighthouse\Schema\Directives\ConvertEmptyStringsToNullDirective::class, | |
\Nuwave\Lighthouse\Schema\Directives\SanitizeDirective::class, | |
// \Nuwave\Lighthouse\Validation\ValidateDirective::class, // overriding the default Validation | |
\App\Middleware\LighthouseValidateDirectiveFix::class, // with this fixed one | |
\Nuwave\Lighthouse\Schema\Directives\TransformArgsDirective::class, | |
\Nuwave\Lighthouse\Schema\Directives\SpreadDirective::class, | |
\Nuwave\Lighthouse\Schema\Directives\RenameArgsDirective::class, | |
\Nuwave\Lighthouse\Schema\Directives\DropArgsDirective::class, | |
], |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// app/Middleware/LighthouseValidateDirectiveFix.php | |
declare(strict_types=1); | |
namespace App\Middleware; | |
use GraphQL\Type\Definition\ResolveInfo; | |
use Illuminate\Container\Container; | |
use Illuminate\Contracts\Validation\Factory as ValidationFactory; | |
use Nuwave\Lighthouse\Exceptions\ValidationException; | |
use Nuwave\Lighthouse\Execution\Arguments\ArgumentSet; | |
use Nuwave\Lighthouse\Schema\Values\FieldValue; | |
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext; | |
use Nuwave\Lighthouse\Validation\RulesGatherer; | |
use Nuwave\Lighthouse\Validation\ValidateDirective; | |
class LighthouseValidateDirectiveFix extends ValidateDirective | |
{ | |
public $error = null; | |
public function handleField(FieldValue $fieldValue): void | |
{ | |
$self = $this; | |
$hasGuardOrCanDirective = $this->fieldHasGuardOrCanDirective($fieldValue); | |
$fieldValue->addArgumentSetTransformer(static function (ArgumentSet $argumentSet, ResolveInfo $resolveInfo) use ($self, $hasGuardOrCanDirective): ArgumentSet { | |
$rulesGatherer = new RulesGatherer($argumentSet); | |
$validationFactory = Container::getInstance()->make(ValidationFactory::class); | |
$validator = $validationFactory->make( | |
$argumentSet->toArray(), | |
$rulesGatherer->rules, | |
$rulesGatherer->messages, | |
$rulesGatherer->attributes, | |
); | |
if ($validator->fails()) { | |
$path = implode('.', $resolveInfo->path); | |
$error = new ValidationException("Validation failed for the field [{$path}].", $validator); | |
if ($hasGuardOrCanDirective) { | |
$self->error = $error; | |
} else { | |
throw $error; | |
} | |
} | |
return $argumentSet; | |
}); | |
$fieldValue->wrapResolver(fn (callable $resolver): \Closure => function (mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) use ($resolver, $self) { | |
if ($self->error) { | |
// Saving the error in the custom graphqlcontext | |
$context->setValidationError($self->error); | |
} | |
return $resolver($root, $args, $context, $resolveInfo); | |
}); | |
} | |
protected function fieldHasGuardOrCanDirective(FieldValue $fieldValue): bool | |
{ | |
/** @var DirectiveNode $directive */ | |
foreach ($fieldValue->getField()->directives as $directive) { | |
if (in_array($directive->name->value, ['guard', 'can'])) { | |
return true; | |
} | |
} | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment