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
ValidationType
which 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 @lower
and it’s nearly impossible to horribly awkward. The mechanic for normaltypes
works great, but forinput
it 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
validationErrors
extra argument that is injected into such fields, it’s populated whenever an input field is nullable, matches the output behavior.Hi @carloschneider,
args
is only definition from schema, you passpredicate
function to filter whitch mutations have input.Got the same issue, just need to figure out a way to access
context
somehow insidevisitInputFieldDefinition
.@gaillota This indeed works for visitFieldDefinition but an input doesn’t have a resolve function so that doesn’t work with visitInputDefinition.
visitInputDefinition
seems to be called on schema creation therefore no context to be accessed to have auth directives on input.The reason is quite simple,
field
argument invisitInputFieldDefinition
is of typeGraphQLInputField
and there is noresolve
property inGraphQLInputField
.Facing the same issue, wanted to restrict mutation on specific field based on user authorization.