3

I am working on a GraphQL server built using Express and attempting to support Relay.

For a regular GraphQL query, I can handle authorization in the resolve function. E.g.:

var queryType = new GraphQLObjectType({
    name: 'RootQueryType',
    fields: () => ({
        foo: {
            type: new GraphQLList(bar),
            description: 'I should have access to some but not all instances of bar',
            resolve: (root, args, request) => getBarsIHaveAccessTo(request.user)
        }
    })
});

To support Relay refetching on the back-end, Facebook's Relay tutorial instructs us to have GraphQL objects implement a nodeInterface for mapping global ids to objects and objects to GraphQL types. The nodeInterface is defined by the nodeDefinitions function from graphql-relay.

const {nodeInterface, nodeField} = nodeDefinitions(
    (globalId) => {
        const {type, id} = fromGlobalId(globalId);
        if (type === 'bar') {
            // since I don't have access to the request object here, I can't pass the user to getBar, so getBar can't perform authorization
            return getBar(id);
        } else {
            return null;
        }
    },
    (obj) => {
        // return the object type
    }
);

The refetching function that gets passed to nodeDefinitions doesn't get passed the request object, only the global id. How can I get access to the user during refetching so I can authorize those requests?

As a sanity check, I tried querying for nodes that the authenticated user doesn't otherwise have access to (and shouldn't) through the node interface, and got the requested data back:

{node(id:"id_of_something_unauthorized"){
    ... on bar {
        field_this_user_shouldnt_see
    }
}}

=>

{
    "data": {
        "node": {
            "field_this_user_shouldnt_see": "a secret"
        }
    }
}
jwde
  • 642
  • 4
  • 13

1 Answers1

1

As it turns out, the request data actually does get passed to resolve. If we look at the source, we see that nodeDefinitions tosses out the parent parameter and passes the global id, the context (containing the request data), and the info arguments from nodeField's resolve function.

Ultimately, where a resolve call would get the following arguments:

(parent, args, context, info)

the idFetcher instead gets:

(id, context, info)

So we can implement authorization as follows:

const {nodeInterface, nodeField} = nodeDefinitions(
    (globalId, context) => {
        const {type, id} = fromGlobalId(globalId);
        if (type === 'bar') {
            // get Bar with id==id if context.user has access
            return getBar(context.user, id);
        } else {
            return null;
        }
    },
    (obj) => {
        // return the object type
    }
);

https://github.com/graphql/graphql-relay-js/blob/master/src/node/node.js#L94-L102

jwde
  • 642
  • 4
  • 13