Reputation: 1134
I would like to protect some specific fields of a content type to only allow admin user to modify the value but allow users to access it.
Imagine for instance the User
type with a is_admin
field. Only admin should be able to update it but everyone should be able to read it.
type User {
id: ID!
name: String!
email: String!
is_admin: Boolean!
}
The can directive doesn't seem to work with field in mutation. At first I tried adding @can(ability: "setAdmin")
with a custom policy but it didn't had any effect. That same can/policy used on the mutation "worked" but this was not granular enough.
It appears that custom field restrictions using a custom directive should help, but this too doesn't seem to work on a field level in a mutation input type.
type mutation {
updateUser(
input: UpdateUserInput! @spread
): User @update @middleware(checks: ["auth:api"])
}
input UpdateUserInput {
id: ID!
name: String!
email: String!
is_admin: Boolean! @adminOnly
}
With this custom directive in app/GraphQL/Directives/AdminOnlyDirective.php
<?php
namespace App\GraphQL\Directives;
use Closure;
use GraphQL\Type\Definition\ResolveInfo;
use Nuwave\Lighthouse\Exceptions\DefinitionException;
use Nuwave\Lighthouse\Schema\Directives\BaseDirective;
use Nuwave\Lighthouse\Schema\Values\FieldValue;
use Nuwave\Lighthouse\Support\Contracts\DefinedDirective;
use Nuwave\Lighthouse\Support\Contracts\FieldMiddleware;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
class AdminOnlyDirective extends BaseDirective implements FieldMiddleware, DefinedDirective
{
/**
* Name of the directive as used in the schema.
*
* @return string
*/
public function name(): string
{
return 'adminOnly';
}
public static function definition(): string
{
return /** @lang GraphQL */ <<<GRAPHQL
"""
Limit field update to only admin.
"""
directive @adminOnly() on FIELD_DEFINITION
GRAPHQL;
}
public function handleField(FieldValue $fieldValue, Closure $next): FieldValue
{
$originalResolver = $fieldValue->getResolver();
return $next(
$fieldValue->setResolver(
function ($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) use ($originalResolver) {
$user = $context->user();
if (
// Unauthenticated users don't get to see anything
! $user
// The user's role has to match have the required role
|| !$user->is_admin
) {
return null;
}
return $originalResolver($root, $args, $context, $resolveInfo);
}
)
);
}
}
So, is there a way to prevent "update" of specific fields with laravel lighthouse?
Upvotes: 1
Views: 2137
Reputation: 214
For now, you can use https://lighthouse-php.com/4.16/custom-directives/argument-directives.html#argtransformerdirective to transform that field to null
before inserting the database or just throw error out to avoid changes on your specific field, it's like how the @trim behaves;
In lighthouse v5, it's class ArgTransformerDirective
has renamed to ArgSanitizerDirective
and method transform
to sanitize
https://github.com/nuwave/lighthouse/blob/v5.0-alpha.3/src/Schema/Directives/TrimDirective.php
Extra:
I'm still figuring how @can works, cause i still need to drop the whole attribute instead of passing null to my database;
Update: @can only apply to input
type instead of input
type
Upvotes: 1
Reputation: 317
The first idea I have in mind here is to create two different inputs and/or mutations. E.g. for admins with access to the field:
updateUserAsAdmin(
input: UpdateUserFullInput! @spread
): User @update
@middleware(checks: ["auth:api"])
@can("users.update.full")
And UpdateUserFullInput
contains the is_admin
field.
I also came across this discussion a few times: https://github.com/nuwave/lighthouse/issues/325 Maybe you can also find some useful ideas here.
You may also want to look at the official docs: https://github.com/nuwave/lighthouse/blob/master/docs/master/security/authorization.md#custom-field-restrictions
Upvotes: 0