【问题标题】:How to limit query introspection如何限制查询自省
【发布时间】:2018-09-16 18:13:42
【问题描述】:

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

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

据我了解,graphiql 发送特殊的自省查询,其中包含 __schema__type 字段以显示架构及其文档。

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

【问题讨论】:

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


    【解决方案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 界面访问。

    将管理定义放在单独的文件中可能更方便或更不方便,具体取决于您的项目、代码和偏好。

    【讨论】:

    • 如果用户发现 '/graphql-anon' 端点怎么办?
    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      正如 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]);
        }
      }
      

      我仍在寻找具有一个架构的解决方案。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-03-11
        • 2012-07-23
        • 2020-04-17
        • 1970-01-01
        • 2019-09-30
        • 2012-02-22
        • 2012-03-31
        相关资源
        最近更新 更多