ExpediaGroup / graphql-kotlin

Libraries for running GraphQL in Kotlin

Home Page:https://opensource.expediagroup.com/graphql-kotlin/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to know which field triggered a directive when using the same directive multiple times?

fediazgon opened this issue · comments

I'm implementing authentication schemes. I have a directive similar to:

@GraphQLDirective(
    name = "auth",
    description = "auth",
    locations = [ARGUMENT]
)
annotation class AuthDirective(varargs val permissions: PermissionEnum)

I'm annotating my arguments like:

type Query {
  accessProtectedResource(id1: String! auth(...), id2: String! auth(...)): [Int]
}

The goal of AuthDirective is to check that the user has access to id1 and id2 (this is an oversimplification if it doesn't make much sense, but I need to do something like this).

The problem is that when I define my custom wiring:

class AuthDirectiveWiring: KotlinSchemaDirectiveWiring {

    override fun onField(environment: KotlinFieldDirectiveEnvironment): GraphQLFieldDefinition {
        ...
    }
}

I see onField being triggered twice (which makes sense) but I don't have a way of knowing if I should evaluate the permissions regarding id1 or id2. The environment is pretty much the same for both invocations (they are different objects, but structurally are the same).

I've seen some examples in graphql-java where they "evaluate" all directives on every call (https://github.com/graphql-java-kickstart/samples/blob/master/directives/src/main/java/directives/RangeDirective.java) but I rather save the second call. Other examples are independent of which field triggered the invocation because they just use the directive in the environment, but in my case, the behavior depends on the actual field value.

Is there a way of achieving this?

Hello 👋
I think in current code base it will be pretty hard to achieve what you want, i.e. only fields have access to data fetcher but you want to act on individual arguments, i.e. you want to trigger onArgument wiring... but only onField has access to data fetcher (as it will have coordinates).

Probably out of the question, but in general I'd recommend to define those @auth directives as field level directives instead as applying it on args seems kind of odd.

Hi, thanks for the prompt response. The thing is that onArgument as well as onField are triggered. This is how I annotated my Kotlin query:

object MyQuery : Query {
    suspend fun accessProtectedResource(
        env: DataFetchingEnvironment,
        @auth(...) id1: String,
        @auth(...) id2: String,
    ): Int = env.dataLoaders()...
}

So in onField I have access to all argument binds via DataFetchingEnvironment#arguments, however, given that there are two directives, onField gets triggered twice.

My problem is not being able to access the dataFetchers (which I already have), but to know when a specific invocation to onField was related with id1 or id2.

So in onField I have access to all argument binds via DataFetchingEnvironment#arguments, however, given that are two directives, onField gets triggered twice.

My problem is not being able to access the dataFetchers (which I already have), but to know when a specific invocation to onField was related with id1 or id2.

Yes wiring is triggered twice -> once for each element that has directive. My point was that since directive is applied on argument you should be specifying argument re-wiring logic (which doesn't have access to data fetcher).

You could play around with doing some combination of custom schema hook and/or KotlinDirectiveWiringFactory but again IMHO that is still somewhat odd pattern (validating args vs validating field access).

i.e.

type Query {
  foo(id: ID, id2: ID): Foo @auth(scopes: [["scope1"], ["scope2 scope3"]])
  #vs
  foo(@auth("scope1") id ID, @auth("scope2 scope3") id2 ID): Foo    # <--- this seems "weird" to me

See how its implemented in @requiresScope Federation directive.

Thanks for the help. I'll take a look.