如何限制查询内省

时间:2018-04-06 16:42:29

标签: graphql apollo graphql-js apollo-server graphql-tools

我有一个由apollo-server驱动的node.js项目。我使用自定义@admin指令对命令,突变和对象字段进行权限检查。对于查询和变异,此指令会抛出错误,对于字段,它返回null而不是实际值。

现在,我想将graphiql ui添加到我的项目中,因此开发人员可以探索我的graphql架构。但是,我希望他们看到匿名用户看到的架构,即他们不应该知道@admin字段和@admin查询以及所有突变(甚至是非管理员)的存在。即使那些拥有执行这些操作凭据的人(即以管理员身份登录)也不应该看到架构的那些部分。

据我了解,graphiql发送特殊内省查询,其中包含__schema__type字段以显示模式及其文档。

是否有可能以某种方式修改我的架构,该架构是使用makeExecutableSchema中的graphql-tools构建的,以实现我的目标?

3 个答案:

答案 0 :(得分:1)

这是一种方法

您可以为主端点使用扩展架构,并使用相同的架构,而不使用graphiql端点的扩展。

我们以此架构定义为例:

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

可执行模式:

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

现在,让我们借助extend关键字拆分架构定义。  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
}

为避免对架构中未定义的解析器发出一些警告,您可能希望将管理员相关的解析器拆分为另一个文件或另一个解析器映射。

现在您将拥有2个可执行模式:

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 */)
})

您的主端点应使用扩展架构。

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

由于GraphiQL端点需要GraphQL端点,因此您必须专门为第二个模式创建另一个端点。也许是这样的:

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

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

就是这样!

现在大多数代码都是共享的,只有部分模式可以使用GraphiQL接口访问。

根据您的项目,代码和首选项,将管理员定义放在单独的文件中可能会更方便或更不方便。

答案 1 :(得分:1)

使用graphql-introspection-filtering,可以使用单个模式来实现。文档中有一个带有身份验证指令的示例。我仅使用简单的用例进行了测试,但是看起来很有希望。

我用graphql@^14.0.0graphql-tools@^4.0.0尝试过。

答案 2 :(得分:0)

建议有两个架构,如 Tal Z 所示。只有我创建两个相同的模式,然后修改一个:

import { makeExecutableSchema, visitSchema } from 'graphql-tools';
import { LimitIntrospection } from './IntrospectionVisitor';

export async function getGraphiqlSchema(): Promise<GraphQLSchema> {
  if (!graphiqlSchema) {
    graphiqlSchema = await loadSchema(); // same as for "private" schema
    const visitor = new LimitIntrospection();
    visitSchema(graphiqlSchema, () => [visitor]);
  }
  return graphiqlSchema;
}

这是访客。试图访问字段,但无法从字段访问者中删除其父项中的字段。所以求助于访问对象。还必须将_mutationType设置为null,因为您无法删除所有字段形式的变异。

import { DirectiveNode, GraphQLField, GraphQLObjectType, GraphQLSchema } from 'graphql';
import { SchemaVisitor } from 'graphql-tools';

const isAdmin = (field: GraphQLField<any, any>): boolean =>
  !!field.astNode &&
  !!field.astNode.directives &&
  field.astNode.directives.some((d: DirectiveNode) => d.name.value === 'admin');

export class LimitIntrospection extends SchemaVisitor {

  visitSchema(schema: GraphQLSchema) {
    (schema as any)._mutationType = null;
  }

  visitObject(object: GraphQLObjectType) {
    const fields = object.getFields();
    const adminKeys = Object.values(fields).reduce(
      (ak, field) => isAdmin(field) ? [...ak, field.name] : ak,
      [],
    );
    adminKeys.forEach(key => delete fields[key]);
  }
}

我仍然在寻找一种架构的解决方案。