graphql-tools: SchemaDirectiveVisitor.visitInputFieldDefinition resolver doesn't fire
While attempting to build a custom auth directive I’m unable to get wrapped resolvers to invoke when working with Input types. Is this the expected behavor? In my case I’d like to use a custom directive to limit write access via mutation input arguments. Consider the following example
const { defaultFieldResolver } = require('graphql')
const { SchemaDirectiveVisitor, makeExecutableSchema } = require('graphql-tools')
const { graphqlExpress } = require('apollo-server-express')
const bodyParser = require('body-parser')
const express = require('express')
const app = express()
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({
extended: true
}))
const typeDefs = `
directive @auth(
requires: Role = USER,
action: Action = READ,
) on INPUT_FIELD_DEFINITION
enum Role {
ADMIN
USER
}
enum Action {
READ
MODIFY
}
type Author {
id: ID!
firstName: String
lastName: String
role: String
}
input AuthorInput {
id: ID!
firstName: String
lastName: String
role: String @auth(requires: ADMIN, action: MODIFY)
}
type Mutation {
submitUser(
author: AuthorInput!
): Author
}
type Query {
authors: [Author]
}
schema {
query: Query
mutation: Mutation
}
`
const resolvers = {
Mutation: {
submitUser (_, { author }) {
// save author
console.log('saving author...')
return author
}
}
}
class AuthDirective extends SchemaDirectiveVisitor {
visitInputFieldDefinition (field, details) {
console.log('visitInputFieldDefinition')
const { resolve = defaultFieldResolver } = field
field.resolve = function (...args) {
console.log('Custom resolver')
// Auth logic would go here
return resolve.apply(this, args)
}
}
}
const executableSchema = makeExecutableSchema({
typeDefs,
resolvers,
schemaDirectives: { auth: AuthDirective }
})
app.use('/graphql', graphqlExpress((request) => {
return {
schema: executableSchema
}
}))
app.listen(8000, () => console.log(':8000 Listening'))
Starting the server prints
visitInputFieldDefinition
:8000 Listening
Submitting the following graphQL mutation
mutation addPerson {
submitUser(author: {
id: "123"
firstName: "Payton"
lastName: "Manning"
role: "admin"
}) {
id
role
}
}
Prints
saving author...
I’d expect to see
Invoking custom resolver
saving author...
Most examples I see that use custom directives and input types only change filed.type, but I’m hoping to invoke some custom auth logic before the mutation resolver runs.
#640 seems like it should help here but I’ve been unable to get my auth resolver to invoke.
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 47
- Comments: 21 (1 by maintainers)
Hi guys,
I have implemented a directive that is checking access control for Input Fields on mutations. My implementation gives access to context and value on the input field. ( Of course, it’s proof of concept but it can be helpful for someone)
Here you go. It works a bit differently from the example I linked before. I prefer one directive per constraint rather than smashing them all together.
This works by defining a
ValidationTypewhich wraps the original type (through the directive definition) and adds validation when it is parsed. If it fails, it throws aValidationError. I use the validator NPM package to do the actual validation logic.@sami616 you should be able to get the parent from the details, and then all the fields from the parent
Closing for now as working as designed with package from community available to help streamline.
This problem is really challenging to work around. We’d really like to allow basic data transforms on our input types to reduce code bloat. We often have stuff where we want to do something like
email: String! @trim @lowerand it’s nearly impossible to horribly awkward. The mechanic for normaltypesworks great, but forinputit feels basically impossible.Using @cristo-rabani 's code above I was able to do something similar in typescript(with a lot of ts-ignores). This might be handy for anyone else who is trying to make it work in typescript or might find the variable names unclear, although it’s still pretty terse:
Seriously, graphql should trye to make this easier. Obviously it’s a hard problem, based on the above code, but clearly a lot of people need it! We shouldn’t have to do something so crazy. 🙃
Solved this with https://github.com/profusion/apollo-validation-directives/ but it wasn’t that simple, had to mark all input types requiring validation, then walk all the fields to see if any had input arguments that would lead to the validated type (even nested), if so then wrap the resolver to first validate the argument (or nested) and just after that call the resolver. That also introduces a
validationErrorsextra argument that is injected into such fields, it’s populated whenever an input field is nullable, matches the output behavior.Hi @carloschneider,
argsis only definition from schema, you passpredicatefunction to filter whitch mutations have input.Got the same issue, just need to figure out a way to access
contextsomehow insidevisitInputFieldDefinition.@gaillota This indeed works for visitFieldDefinition but an input doesn’t have a resolve function so that doesn’t work with visitInputDefinition.
visitInputDefinitionseems to be called on schema creation therefore no context to be accessed to have auth directives on input.The reason is quite simple,
fieldargument invisitInputFieldDefinitionis of typeGraphQLInputFieldand there is noresolveproperty inGraphQLInputField.Facing the same issue, wanted to restrict mutation on specific field based on user authorization.