5

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

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

据我了解,graphiql 发送特殊的自省查询,其中包含显示模式__schema及其__type文档的字段。

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

4

3 回答 3

1

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

我用graphql@^14.0.0and试过了graphql-tools@^4.0.0

于 2019-01-10T21:19:47.920 回答
1

这是一种方法

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

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

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

和可执行模式:

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

extend现在,让我们在关键字 的帮助下拆分模式定义。在此处阅读有关扩展类型和extend关键字的信息。

// 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 接口访问。

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

于 2018-04-07T11:34:01.253 回答
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]);
  }
}

我仍在寻找具有一个模式的解决方案。

于 2018-04-08T10:37:03.567 回答