Graphql 如何限制查询自省

Graphql 如何限制查询自省,graphql,apollo,graphql-js,apollo-server,graphql-tools,Graphql,Apollo,Graphql Js,Apollo Server,Graphql Tools,我有一个node.js项目,由apollo服务器提供支持。我使用定制的@admin指令对查询、突变和对象字段进行权限检查。对于查询和变异,该指令抛出错误,对于字段,它返回null而不是实值 现在,我想将graphiql ui添加到我的项目中,以便其他开发人员可以探索我的graphql模式。但是,我想让他们看到匿名用户看到的模式,也就是说,他们不应该知道是否存在@admin字段和@admin查询以及所有突变(即使是非管理员)。即使那些拥有执行这些操作的凭据(即以管理员身份登录)的人也不应该看到架构

我有一个node.js项目,由apollo服务器提供支持。我使用定制的
@admin
指令对查询、突变和对象字段进行权限检查。对于查询和变异,该指令抛出错误,对于字段,它返回null而不是实值

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

据我所知,graphiql发送特殊的内省查询,其中包含
\uuu schema
\uu type
字段以显示schema及其文档

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

这里有一种方法 您可以对主端点使用扩展模式,对graphiql端点使用相同的模式而不使用扩展

让我们以这个模式定义为例:

// schemaDef
type Query {
  anonQuery: QueryResult
  adminQuery: AdminQueryResult @admin
}
以及可执行模式:

const schema = makeExecutableSchema({
  typeDefs: [schemaDef /* ... additional schema files */],
  resolvers: merge(schemaResolvers/* ... additional resolvers */)
})  
现在,让我们借助
extend
关键字拆分模式定义。

为了避免出现架构中未定义冲突解决程序的警告,您可能需要将与管理相关的冲突解决程序拆分到另一个文件或另一个冲突解决程序映射

现在您将有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端点,因此必须专门为第二个模式创建另一个GraphQL端点。也许是这样的:

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

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

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


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

最终有了两个模式,正如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;
}
从“graphql工具”导入{makeExecutableSchema,visitSchema};
从“./IntrospectionVisitor”导入{LimitIntrospection};
导出异步函数getGraphiqlSchema():Promise{
if(!graphiqlSchema){
graphiqlSchema=await loadSchema();//与“private”架构相同
const visitor=new LimitIntrospection();
visitSchema(graphiqlSchema,()=>[visitor]);
}
返回graphiqlSchema;
}
客人来了。试图访问字段,但无法从字段访问者内部从其父字段中删除字段。于是就求助于参观物品。还必须将_mutationType设置为null,因为不能从mutation中删除所有字段

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]);
  }
}
import{DirectiveNode,GraphQLField,GraphQLObjectType,GraphQLSchema}来自“graphql”;
从“graphql工具”导入{SchemaVisitor};
常量isAdmin=(字段:GraphQLField):布尔=>
!!field.astNode&&
!!field.astNode.directions&&
field.astNode.directives.some((d:DirectiveNode)=>d.name.value==='admin');
导出类LimitIntrospection扩展了SchemaVisitor{
visitSchema(模式:GraphQLSchema){
(架构如有)。\u mutationType=null;
}
visitObject(对象:GraphQLObjectType){
const fields=object.getFields();
const adminKeys=Object.values(fields).reduce(
(ak,field)=>isAdmin(field)?[…ak,field.name]:ak,
[],
);
forEach(key=>delete字段[key]);
}
}
我仍然在寻找一个具有一个模式的解决方案。

使用一个模式就可以实现它。文档中有一个带有身份验证指令的示例。我只使用简单的用例测试了它,但它看起来很有希望


我用
graphql@^14.0.0
graphql工具^4.0.0

尝试了它,但是如果用户发现“/graphql anon”端点怎么办?
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]);
  }
}