mercurius-js / auth

Mercurius Auth Plugin

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How I can set custom directives to the current field?

dmitrydrynov opened this issue · comments

Hi guys,
How I can set custom directives to the current field through Javascript code?

I created a custom Directive:

const authDirective = new GraphQLDirective({
    name: 'auth',
    locations: [DirectiveLocation.OBJECT, DirectiveLocation.FIELD_DEFINITION],
    args: ...
})

Added to the schema:

const schema = new GraphQLSchema({
    directives: [authDirective],
    query: new GraphQLObjectType({
        fields: {
            secretData: ...
        }
    })
})

How I can use my directive only for the secretData field?

I tried to add a directives parameter for field definition but it doesn't work:

{
    type: validateWidgetQLType,
    args: validateWidgetQLArgs,
    directives: [
        { name: 'auth', arguments: { requires: 0 }}
    ],
}

Please help!

Hello! :) I'm not familiar with defining a schema in this way - but I can lend a hand for sure! When you say it doesn't work, could you clarify what you mean?

  • It doesn't show the auth directive for the secretData field in the instantiated GraphQLSchema?
  • Or mercurius-auth doesn't work correctly for this schema definition (that has the custom directive correctly loaded)?

One good point of reference is the graphql-js typings: https://graphql.org/graphql-js/type/#graphqlobjecttype (GraphQLFieldConfig )

Both
When I print the schema as SDL I don't see the @auth directive for the secretData field.

directive @auth(requires: Role = ADMIN) on OBJECT | FIELD_DEFINITION
type Query {
        secretData(x: Int, y: Int): Int
}

In mercurius-auth a code in applyPolicy() is never executed

Cool thanks, makes sense that mercurius-auth applyPolicy doesn't run if the directive isn't registered for that field in the schema. Could you provide the schema AST JSON?

{
  "kind": "Document",
  "definitions": [
    {
      "kind": "DirectiveDefinition",
      "name": {
        "kind": "Name",
        "value": "auth"
      },
      "arguments": [
        {
          "kind": "InputValueDefinition",
          "name": {
            "kind": "Name",
            "value": "requires"
          },
          "type": {
            "kind": "NamedType",
            "name": {
              "kind": "Name",
              "value": "UserRole"
            }
          },
          "defaultValue": {
            "kind": "EnumValue",
            "value": "GUEST"
          },
          "directives": []
        }
      ],
      "repeatable": false,
      "locations": [
        {
          "kind": "Name",
          "value": "OBJECT"
        },
        {
          "kind": "Name",
          "value": "FIELD_DEFINITION"
        }
      ]
    },
    {
      "kind": "ObjectTypeDefinition",
      "name": {
        "kind": "Name",
        "value": "Mutation"
      },
      "interfaces": [],
      "directives": [],
      "fields": [
        {
          "kind": "FieldDefinition",
          "name": {
            "kind": "Name",
            "value": "validateWidget"
          },
          "arguments": [
            {
              "kind": "InputValueDefinition",
              "name": {
                "kind": "Name",
                "value": "step"
              },
              "type": {
                "kind": "NamedType",
                "name": {
                  "kind": "Name",
                  "value": "String"
                }
              },
              "directives": []
            },
            {
              "kind": "InputValueDefinition",
              "name": {
                "kind": "Name",
                "value": "formData"
              },
              "type": {
                "kind": "NamedType",
                "name": {
                  "kind": "Name",
                  "value": "JSONObject"
                }
              },
              "directives": []
            }
          ],
          "type": {
            "kind": "NamedType",
            "name": {
              "kind": "Name",
              "value": "validateWidgetType"
            }
          },
          "directives": []
        }
      ]
    },
    {
      "kind": "ObjectTypeDefinition",
      "name": {
        "kind": "Name",
        "value": "validateWidgetType"
      },
      "interfaces": [],
      "directives": [],
      "fields": [
        {
          "kind": "FieldDefinition",
          "name": {
            "kind": "Name",
            "value": "step"
          },
          "arguments": [],
          "type": {
            "kind": "NonNullType",
            "type": {
              "kind": "NamedType",
              "name": {
                "kind": "Name",
                "value": "String"
              }
            }
          },
          "directives": []
        },
        {
          "kind": "FieldDefinition",
          "name": {
            "kind": "Name",
            "value": "nextStep"
          },
          "arguments": [],
          "type": {
            "kind": "NamedType",
            "name": {
              "kind": "Name",
              "value": "String"
            }
          },
          "directives": []
        },
        {
          "kind": "FieldDefinition",
          "name": {
            "kind": "Name",
            "value": "validated"
          },
          "arguments": [],
          "type": {
            "kind": "NonNullType",
            "type": {
              "kind": "NamedType",
              "name": {
                "kind": "Name",
                "value": "Boolean"
              }
            }
          },
          "directives": []
        },
        {
          "kind": "FieldDefinition",
          "name": {
            "kind": "Name",
            "value": "error"
          },
          "arguments": [],
          "type": {
            "kind": "NamedType",
            "name": {
              "kind": "Name",
              "value": "String"
            }
          },
          "directives": []
        },
        {
          "kind": "FieldDefinition",
          "name": {
            "kind": "Name",
            "value": "token"
          },
          "arguments": [],
          "type": {
            "kind": "NamedType",
            "name": {
              "kind": "Name",
              "value": "String"
            }
          },
          "directives": []
        },
        {
          "kind": "FieldDefinition",
          "name": {
            "kind": "Name",
            "value": "data"
          },
          "arguments": [],
          "type": {
            "kind": "NamedType",
            "name": {
              "kind": "Name",
              "value": "JSONObject"
            }
          },
          "directives": []
        }
      ]
    },
    {
      "kind": "EnumTypeDefinition",
      "name": {
        "kind": "Name",
        "value": "UserRole"
      },
      "directives": [],
      "values": [
        {
          "kind": "EnumValueDefinition",
          "name": {
            "kind": "Name",
            "value": "ADMIN"
          },
          "directives": []
        },
        {
          "kind": "EnumValueDefinition",
          "name": {
            "kind": "Name",
            "value": "CLIENT"
          },
          "directives": []
        },
        {
          "kind": "EnumValueDefinition",
          "name": {
            "kind": "Name",
            "value": "USER"
          },
          "directives": []
        },
        {
          "kind": "EnumValueDefinition",
          "name": {
            "kind": "Name",
            "value": "GUEST"
          },
          "directives": []
        }
      ]
    }
  ]
}

you don't happen to have the fully resolved AST do you? As name is coming up as [Object] etc. Something like: JSON.stringify(schemaAST, null, 2) should do nicely!

updated above

Cool, thanks - I can take a closer look this evening :) In the meantime, I can point you to this thread, which is a good read on the subject: graphql/graphql-js#1343 - from an initial glance, I'm not sure if it's currently possible to define them in this way (this is backed up by the fact I can see directive definitions but no usages, so I don't think it's a mercurius-auth issue)

Just taken a closer look and based on the thread in the issue I linked, it currently looks like directives cannot be defined in this way (this is reflected in the AST omitted, as it doesn't contain any directive usage). Have you considered any alternative approaches?

Can we support defining the auth behavior outside of the schema with a config object?

Great idea! In theory, yes, as I think it would be a case of an extra lookup within the config object as well as the schema (when traversing the schema tree to (re-)define the field resolvers for protected fields)? And if we get a match, we run applyPolicy as normal

The only issue I can see is when we have a field that already contains a matching directive, maybe we just run the config applyPolicy first and then the schema applyPolicy? Or even support one mode or the other exclusively! What do you think?

I would just support only one mode, via an option.

I would just support only one mode, via an option.

Agreed - will certainly remove any edges cases that way

@dmitrydrynov would you like to implement this feature?

@dmitrydrynov would you like to implement this feature?

Hi @dmitrydrynov :) not sure if you missed my comment - would this be something you would be interested in contributing to? I was thinking of starting the implementation for this when I'm next free, but didn't want to step on your toes in case you had started it or wanted to do this!