graphql-introspection-filtering
Advanced tools
Comparing version 3.0.0-beta to 3.0.0
@@ -16,3 +16,2 @@ "use strict"; | ||
Object.defineProperty(exports, "IntrospectionMapperKind", { enumerable: true, get: function () { return types_1.IntrospectionMapperKind; } }); | ||
// todo docs | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "graphql-introspection-filtering", | ||
"version": "3.0.0-beta", | ||
"version": "3.0.0", | ||
"description": "Filter graphql schema introspection result to hide restricted fields and types", | ||
@@ -5,0 +5,0 @@ "main": "./dist/index.js", |
270
README.md
@@ -32,6 +32,7 @@ # graphql-introspection-filtering | ||
Filtering is possible on schemas created with `makeExecutableSchema`, provided by `graphql-introspection-filtering`. | ||
To enable filtering, a mapper has to be applied to schema. | ||
``` | ||
import makeExecutableSchema from 'graphql-introspection-filtering'; | ||
import makeExecutableSchema, { mapSchema } from 'graphql-introspection-filtering'; | ||
const schema = makeExecutableSchema(schemaConfig[, builder]); | ||
export default mapSchema(makeExecutableSchema(schemaConfig[, builder]), mapper); | ||
``` | ||
@@ -43,81 +44,68 @@ | ||
| Option | Type | Default | Description | | ||
|---------------------|------------------------------------------|---------|-------------| | ||
| Option | Type | Default | Description | | ||
|---------------------|------------------------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ||
| **shouldSkipQuery** | `null`, `number`, `(context) => boolean` | `null` | When positive number provided, this number of introspection queries will be unfiltered. Alternatively callback can be provided, it takes `context` as an argument, and should return boolean. | | ||
| **hookDirectives** | `boolean`, `string[]` | `true` | Whether to hook directives, array of specific directive names can be provided | | ||
- `builder` - builder function (default: original graphql `makeExecutableSchema`) | ||
- `mapper` - introspection schema mapper | ||
### Create introspection schema visitor | ||
### Create introspection schema mapper | ||
Every object and field is visited by a directive visitor, where corresponding directive is applied on it in | ||
a schema definition (directives on directives are not allowed, so all directives pass this requirement) | ||
**AND** assigned directive visitor contains a corresponding introspection visitor method. | ||
a schema definition (directives configured separately) **AND** applied mapper contains a | ||
corresponding introspection visitor method. | ||
Directive visitor instance is created for every object and field it was applied to (and all directive definitions). | ||
When *falsy* value is returned by a mapped resolver, the field / object is excluded from introspection result. | ||
When *falsy* value is returned by a visitor, the field / object is excluded from introspection result. | ||
Example introspection mapper can be found below. | ||
Example introspection directive visitor below. | ||
```ts | ||
class AuthenticationDirective<TArgs = any, TContext = any> extends SchemaDirectiveVisitor<TArgs, TContext> implements IntrospectionDirectiveVisitor<TArgs, TContext> { | ||
name: string; // name of the directive used in schema | ||
args: TArgs; // arguments provided to directive | ||
visitedType: VisitableSchemaType; // parent of visited object/field/... | ||
context: TContext; // current query context | ||
// (optional) If defined instance can visit field argument definitions | ||
visitIntrospectionArgument(result: GraphQLArgument, info: GraphQLResolveInfo): IntrospectionVisitor<GraphQLArgument> { | ||
return this.authenticate(result); | ||
} | ||
// (optional) If defined instance can visit `directive` definitions | ||
visitIntrospectionDirective(result: GraphQLDirective, info: GraphQLResolveInfo): IntrospectionVisitor<GraphQLDirective> { | ||
return this.authenticate(result); | ||
} | ||
// (optional) If defined instance can visit `enum` definitions | ||
visitIntrospectionEnum(result: GraphQLEnumType, info: GraphQLResolveInfo): IntrospectionVisitor<GraphQLEnumType> { | ||
return this.authenticate(result); | ||
} | ||
// (optional) If defined instance can visit enum value definitions | ||
visitIntrospectionEnumValue(result: GraphQLEnumValue, info: GraphQLResolveInfo): IntrospectionVisitor<GraphQLEnumValue> { | ||
return this.authenticate(result); | ||
} | ||
// (optional) If defined instance can visit object field definitions | ||
visitIntrospectionField(result: GraphQLField<any, any>, info: GraphQLResolveInfo): IntrospectionVisitor<GraphQLField<any, any>> { | ||
return this.authenticate(result); | ||
} | ||
// (optional) If defined instance can visit input field definitions | ||
visitIntrospectionInputField(result: GraphQLInputField, info: GraphQLResolveInfo): IntrospectionVisitor<GraphQLInputField> { | ||
return this.authenticate(result); | ||
} | ||
// (optional) If defined instance can visit `input` object definitions | ||
visitIntrospectionInputObject(result: GraphQLInputObjectType, info: GraphQLResolveInfo): IntrospectionVisitor<GraphQLInputObjectType> { | ||
return this.authenticate(result); | ||
} | ||
// (optional) If defined instance can visit `interface` definitions | ||
visitIntrospectionInterface(result: GraphQLInterfaceType, info: GraphQLResolveInfo): IntrospectionVisitor<GraphQLInterfaceType> { | ||
return this.authenticate(result); | ||
} | ||
// (optional) If defined instance can visit object `type` definitions | ||
visitIntrospectionObject(result: GraphQLObjectType, info: GraphQLResolveInfo): IntrospectionVisitor<GraphQLObjectType> { | ||
return this.authenticate(result); | ||
} | ||
// (optional) If defined instance can visit `scalar` definitions | ||
visitIntrospectionScalar(result: GraphQLScalarType, info: GraphQLResolveInfo): IntrospectionVisitor<GraphQLScalarType> { | ||
return this.authenticate(result); | ||
} | ||
// (optional) If defined instance can visit `union` definitions | ||
visitIntrospectionUnion(result: GraphQLUnionType, info: GraphQLResolveInfo): IntrospectionVisitor<GraphQLUnionType> { | ||
return this.authenticate(result); | ||
} | ||
} | ||
export const mapper = { | ||
// (optional) If defined instance can visit `scalar` definitions | ||
[IntrospectionMapperKind.SCALAR_TYPE](result: GraphQLScalarType, parent: any, schema: GraphQLSchema) { | ||
return wrap(result, schema, introspectionResolver); | ||
}, | ||
// (optional) If defined instance can visit `enum` definitions | ||
[IntrospectionMapperKind.ENUM_TYPE](result: GraphQLEnumType, parent: any, schema: GraphQLSchema) { | ||
return wrap(result, schema, introspectionResolver); | ||
}, | ||
// (optional) If defined instance can visit object `type` definitions | ||
[IntrospectionMapperKind.OBJECT_TYPE](result: GraphQLObjectType, parent: any, schema: GraphQLSchema) { | ||
return wrap(result, schema, introspectionResolver); | ||
}, | ||
// (optional) If defined instance can visit `input` object definitions | ||
[IntrospectionMapperKind.INPUT_OBJECT_TYPE](result: GraphQLInputObjectType, parent: any, schema: GraphQLSchema) { | ||
return wrap(result, schema, introspectionResolver); | ||
}, | ||
// (optional) If defined instance can visit `union` definitions | ||
[IntrospectionMapperKind.UNION_TYPE](result: GraphQLUnionType, parent: any, schema: GraphQLSchema) { | ||
return wrap(result, schema, introspectionResolver); | ||
}, | ||
// (optional) If defined instance can visit `interface` definitions | ||
[IntrospectionMapperKind.INTERFACE_TYPE](result: GraphQLInterfaceType, parent: any, schema: GraphQLSchema) { | ||
return wrap(result, schema, introspectionResolver); | ||
}, | ||
// (optional) If defined instance can visit enum value definitions | ||
[IntrospectionMapperKind.ENUM_VALUE](result: GraphQLEnumValue, parent: any, schema: GraphQLSchema) { | ||
return wrap(result, schema, introspectionResolver); | ||
}, | ||
// (optional) If defined instance can visit object field definitions | ||
[IntrospectionMapperKind.OBJECT_FIELD](result: GraphQLField<any, any>, parent: any, schema: GraphQLSchema) { | ||
return wrap(result, schema, introspectionResolver); | ||
}, | ||
// (optional) If defined instance can visit field argument definitions | ||
[IntrospectionMapperKind.ARGUMENT](result: GraphQLArgument, parent: any, schema: GraphQLSchema) { | ||
return wrap(result, schema, introspectionResolver); | ||
}, | ||
// (optional) If defined instance can visit input field definitions | ||
[IntrospectionMapperKind.INPUT_OBJECT_FIELD](result: GraphQLInputField, parent: any, schema: GraphQLSchema) { | ||
return wrap(result, schema, introspectionResolver); | ||
}, | ||
// (optional) If defined instance can visit `directive` definitions | ||
[IntrospectionMapperKind.DIRECTIVE](result: GraphQLDirective) { | ||
return wrap(result, schema, resolver); | ||
} | ||
} satisfies IntrospectionSchemaMapper & SchemaMapper; | ||
``` | ||
@@ -160,58 +148,102 @@ | ||
#### AuthenticationDirective | ||
#### Authentication mapper | ||
```ts | ||
class AuthenticationDirective extends SchemaDirectiveVisitor implements IntrospectionDirectiveVisitor { | ||
async authenticate(result: any) { | ||
if (!this.context.roles.includes(this.args.requires || 'ADMIN')) { | ||
return null; | ||
} | ||
return result; | ||
} | ||
visitIntrospectionArgument(result: GraphQLArgument, info: GraphQLResolveInfo): IntrospectionVisitor<GraphQLArgument> { | ||
return this.authenticate(result); | ||
} | ||
type WrappedResolverType<TSource, TContext, TArgs = any, TResult = unknown> = ( | ||
directive: Record<string, any>, orig: GraphQLFieldResolver<TSource, TContext, TArgs, TResult>, | ||
...passtrough: Parameters<GraphQLFieldResolver<TSource, TContext, TArgs>> | ||
) => TResult; | ||
visitIntrospectionDirective(result: GraphQLDirective, info: GraphQLResolveInfo): IntrospectionVisitor<GraphQLDirective> { | ||
return this.authenticate(result); | ||
} | ||
const check = (context: ContextType, requiredPermission?: string) => { | ||
// permission check | ||
}; | ||
visitIntrospectionEnum(result: GraphQLEnumType, info: GraphQLResolveInfo): IntrospectionVisitor<GraphQLEnumType> { | ||
return this.authenticate(result); | ||
} | ||
const introspectionResolver = ( | ||
directive: Record<string, any>, orig: GraphQLFieldResolver<any, any>, | ||
parent: unknown, args: Record<string, unknown>, context: ContextType, info: GraphQLResolveInfo | ||
) => { | ||
if (check(context, directive.requires)) { | ||
return orig(parent, args, context, info); | ||
} | ||
visitIntrospectionEnumValue(result: GraphQLEnumValue, info: GraphQLResolveInfo): IntrospectionVisitor<GraphQLEnumValue> { | ||
return this.authenticate(result); | ||
} | ||
return null; | ||
}; | ||
visitIntrospectionField(result: GraphQLField<any, any>, info: GraphQLResolveInfo): IntrospectionVisitor<GraphQLField<any, any>> { | ||
return this.authenticate(result); | ||
} | ||
const resolver = ( | ||
directive: Record<string, any>, orig: GraphQLFieldResolver<any, any>, | ||
parent: unknown, args: Record<string, unknown>, context: ContextType, info: GraphQLResolveInfo | ||
) => { | ||
if (!check(context, directive.requires)) { | ||
throw new ValidationError(`Cannot query field "${info.fieldName}" on type "${info.parentType.name}".`); | ||
} | ||
visitIntrospectionInputField(result: GraphQLInputField, info: GraphQLResolveInfo): IntrospectionVisitor<GraphQLInputField> { | ||
return this.authenticate(result); | ||
} | ||
return orig(parent, args, context, info); | ||
}; | ||
visitIntrospectionInputObject(result: GraphQLInputObjectType, info: GraphQLResolveInfo): IntrospectionVisitor<GraphQLInputObjectType> { | ||
return this.authenticate(result); | ||
} | ||
visitIntrospectionInterface(result: GraphQLInterfaceType, info: GraphQLResolveInfo): IntrospectionVisitor<GraphQLInterfaceType> { | ||
return this.authenticate(result); | ||
} | ||
type WrappableIntrospectionType = VisitableIntrospectionType & { | ||
resolve: GraphQLFieldResolver<any, any> | null; | ||
subscribe: GraphQLFieldResolver<any, any> | null; // not precise | ||
} | ||
visitIntrospectionObject(result: GraphQLObjectType, info: GraphQLResolveInfo): IntrospectionVisitor<GraphQLObjectType> { | ||
return this.authenticate(result); | ||
} | ||
const isWrappable = (val: any): val is WrappableIntrospectionType => !!val; | ||
visitIntrospectionScalar(result: GraphQLScalarType, info: GraphQLResolveInfo): IntrospectionVisitor<GraphQLScalarType> { | ||
return this.authenticate(result); | ||
} | ||
const wrap = <T extends VisitableIntrospectionType>(result: T, schema: GraphQLSchema, resolver: WrappedResolverType<any, any>) => { | ||
const directive = getDirective(schema, result as any, 'auth')?.[0]; | ||
if (directive && isWrappable(result)) { | ||
const key = result.subscribe ? 'subscribe' : 'resolve'; | ||
const current = result[key]; | ||
visitIntrospectionUnion(result: GraphQLUnionType, info: GraphQLResolveInfo): IntrospectionVisitor<GraphQLUnionType> { | ||
return this.authenticate(result); | ||
result[key] = resolver.bind(undefined, directive, current || defaultFieldResolver); | ||
} | ||
return result; | ||
}; | ||
export const authMapper = { | ||
[IntrospectionMapperKind.SCALAR_TYPE](result: GraphQLScalarType, parent: any, schema: GraphQLSchema) { | ||
return wrap(result, schema, introspectionResolver); | ||
}, | ||
[IntrospectionMapperKind.ENUM_TYPE](result: GraphQLEnumType, parent: any, schema: GraphQLSchema) { | ||
return wrap(result, schema, introspectionResolver); | ||
}, | ||
[IntrospectionMapperKind.OBJECT_TYPE](result: GraphQLObjectType, parent: any, schema: GraphQLSchema) { | ||
return wrap(result, schema, introspectionResolver); | ||
}, | ||
[IntrospectionMapperKind.INPUT_OBJECT_TYPE](result: GraphQLInputObjectType, parent: any, schema: GraphQLSchema) { | ||
return wrap(result, schema, introspectionResolver); | ||
}, | ||
[IntrospectionMapperKind.UNION_TYPE](result: GraphQLUnionType, parent: any, schema: GraphQLSchema) { | ||
return wrap(result, schema, introspectionResolver); | ||
}, | ||
[IntrospectionMapperKind.INTERFACE_TYPE](result: GraphQLInterfaceType, parent: any, schema: GraphQLSchema) { | ||
return wrap(result, schema, introspectionResolver); | ||
}, | ||
[IntrospectionMapperKind.ENUM_VALUE](result: GraphQLEnumValue, parent: any, schema: GraphQLSchema) { | ||
return wrap(result, schema, introspectionResolver); | ||
}, | ||
[IntrospectionMapperKind.OBJECT_FIELD](result: GraphQLField<any, any>, parent: any, schema: GraphQLSchema) { | ||
return wrap(result, schema, introspectionResolver); | ||
}, | ||
[IntrospectionMapperKind.ARGUMENT](result: GraphQLArgument, parent: any, schema: GraphQLSchema) { | ||
return wrap(result, schema, introspectionResolver); | ||
}, | ||
[IntrospectionMapperKind.INPUT_OBJECT_FIELD](result: GraphQLInputField, parent: any, schema: GraphQLSchema) { | ||
return wrap(result, schema, introspectionResolver); | ||
}, | ||
[IntrospectionMapperKind.DIRECTIVE](result: GraphQLDirective) { | ||
if (result.name === 'auth' && isWrappable(result)) { | ||
result.resolve = () => null; | ||
} | ||
// .... | ||
} | ||
return result; | ||
}, | ||
[MapperKind.ENUM_TYPE](result, schema) { | ||
return wrap(result, schema, resolver); | ||
}, | ||
[MapperKind.OBJECT_FIELD](result, _, __, schema) { | ||
return wrap(result as any, schema, resolver); | ||
}, | ||
[MapperKind.INPUT_OBJECT_FIELD](result, _, __, schema) { | ||
return wrap(result as any, schema, resolver); | ||
} | ||
} satisfies IntrospectionSchemaMapper & SchemaMapper; | ||
``` | ||
@@ -221,12 +253,8 @@ | ||
```ts | ||
import makeExecutableSchema from 'graphql-introspection-filtering'; | ||
import makeExecutableSchema, { mapSchema } from 'graphql-introspection-filtering'; | ||
const schema = makeExecutableSchema({ | ||
export default mapSchema(makeExecutableSchema({ | ||
typeDefs: ...schema..., | ||
..., | ||
schemaDirectives: { | ||
auth: AuthenticationDirective | ||
}, | ||
shouldSkipQuery: 1 // skip initial query, which is executeted to hash schema | ||
}); | ||
}), authMapper); | ||
``` |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
66016
1
257
921