Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to limit query introspection

I have a node.js project powered by apollo-server. I use custom @admin directive that does permission checking on queries, mutations and object fields. For queries and mutation this directive throws errors, for fields it returns null instead of real value.

Now, I want to add graphiql ui to my project, so fellow developers can explore my graphql schema. However, I want them to see the schema as anonymous user sees it, i.e. they should not know about existence of @admin fields and @admin queries and all mutations (even non-admin ones). Even those who have credentials to execute these operations (i.e. logged in as admin) should not see those parts of schema.

As far as I understand, graphiql sends special introspection query, which contains __schema and __type fields to display schema and its documentation.

Is it possible to somehow modify my schema, which is constructed using makeExecutableSchema from graphql-tools to achieve my goal?

like image 393
Konstantin Kuznetsov Avatar asked Oct 29 '22 15:10

Konstantin Kuznetsov


1 Answers

Here's one way to do that

You can use an extended schema for the main endpoint and use the same schema without the extension for the graphiql endpoint.

Let's take this schema definition as an example:

// schemaDef
type Query {
  anonQuery: QueryResult
  adminQuery: AdminQueryResult @admin
}

And the executable schema:

const schema = makeExecutableSchema({
  typeDefs: [schemaDef /* ... additional schema files */],
  resolvers: merge(schemaResolvers/* ... additional resolvers */)
})  

Now, let's split the schema definitions with the help of the extend keyword. Read here about Extending Types and the extend keyword.

// anonymous part of the original schema definition:
type Query {
  anonQuery: QueryResult    
}

// admin extensions definitions:
extend type Query {    
  adminQuery: AdminQueryResult @admin
}

To avoid having some warnings about resolvers not defined in the schema, you'll probably want to split the admin related resolvers to another file or another resolver map.

Now you'll have 2 executable schemas:

const mainSchema = makeExecutableSchema({
  typeDefs: [schemaDef /* ... additional schema files */],
  resolvers: merge(schemaResolvers/* ... additional resolvers */)
})

const extendedSchema = makeExecutableSchema({
  typeDefs: [schemaDef, adminSchemaExtensions /* ... additional schema files */],
  resolvers: merge(schemaResolvers, adminSchemaResolvers /* ... additional resolvers */)
})

Your main endpoint should use the extended schema.

router.use('/graphql', /* some middlewares */, graphqlExpress({schema: extendedSchema}))

Since the GraphiQL endpoint expects a GraphQL endpoint, you'll have to create another one specifically for the second schema. Perhaps something like this:

router.use('/graphql-anon', /* some middlewares */, graphqlExpress({schema: mainSchema}))

router.use('/graphiql', /* some middlewares */, graphiqlExpress({endpointURL: '/graphql-anon'}))

That's it!

Now most of your code is shared and only part of the schema is accessible using the GraphiQL interface.

Putting the admin definitions in separate files could be seen as more convenient or less convenient, depending on your project, code and preferences.

like image 51
Tal Z Avatar answered Jan 02 '23 19:01

Tal Z