首页 文章

如何限制查询内省

提问于
浏览
1

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

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

据我所知,graphiql发送特殊的内省查询,其中包含 __schema__type 字段来显示模式及其文档 .

有可能以某种方式修改我的架构,使用 makeExecutableSchemagraphql-tools 构建来实现我的目标吗?

2 回答

  • 1

    这是一种方法

    您可以为主 endpoints 使用扩展架构,并使用相同的架构,而不使用graphiql endpoints 的扩展 .

    我们以此架构定义为例:

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

    您的主 endpoints 应使用扩展架构 .

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

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

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

    That's it!

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

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

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

    我仍然在寻找一个架构的解决方案 .

相关问题