graphql-introspection-filtering
Advanced tools
Comparing version 0.1.3 to 1.0.0
{ | ||
"name": "graphql-introspection-filtering", | ||
"version": "0.1.3", | ||
"version": "1.0.0", | ||
"description": "Filter graphql schema introspection result to hide restricted fields and types", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
172
README.md
# graphql-introspection-filtering | ||
Filter graphql schema introspection result to hide restricted fields and types. | ||
Filter graphql schema introspection result to hide restricted fields and types. | ||
It allows using extended `SchemaDirectiveVisitor`s or filter functions to decide which | ||
schema nodes will be returned with introspection result | ||
> **NOTE:** For successful introspection all dependent types must be returned. | ||
If any of dependent types is missing it won't be possible to rebuild graph on | ||
client side i.e. graphql playground is unable to build interactive documentation. | ||
## Installation | ||
```bash | ||
yarn add graphql-introspection-filtering | ||
``` | ||
## Usage | ||
### Make filtered schema | ||
Most important thing is to wrap executable graphql schema with `makeFilteredSchema` too add filters | ||
``` | ||
const schema = makeFilteredSchema(executableSchema, filters); | ||
``` | ||
- `executableSchema` - Executable schema created with `makeExecutableSchema` | ||
- `filters` - Filters definition | ||
### Filters definition | ||
Object that holds a set of schema node filters | ||
``` | ||
const filters = { | ||
field: [], | ||
type: [], | ||
directive: [] | ||
}; | ||
``` | ||
- `field` - (optional) Array of _filter functions_ to filter fields | ||
- `type` - (optional) Array of _filter functions_ to filter types | ||
- `directive` - (optional) Array of _filter functions_ to filter directives | ||
### Filter function | ||
Node filtering function, when result of this function is `true` the node will be returned | ||
``` | ||
(field, root, args, context, info) => boolean; | ||
``` | ||
- `field` - Schema node to be returned, the node we decide whether we want to show it or not | ||
- `root` - Root node for this introspection request as for regular query | ||
- `args` - Arguments for this introspection request as for regular query | ||
- `context` - Query context for this introspection request as for regular query | ||
- `info` - Query info for this introspection request as for regular query | ||
### Pick filters from schema visitors | ||
This function creates filters definition from `schemaDirectives` based on static methods in | ||
`SchemaDirectiveVisitor`s. | ||
``` | ||
const filters = schemaDirectivesToFilters(schemaDirectives); | ||
``` | ||
- `schemaDirectives` - Set of schemaDirectives provided to `makeExecutableSchema` | ||
## Example with directives | ||
Example filtering schema introspection and checking permissions on fields, | ||
objects and enums with directives | ||
#### Configure graphql schema structure | ||
```graphql schema | ||
enum Role @auth(requires: ADMIN) { | ||
ADMIN | ||
REVIEWER | ||
USER | ||
UNKNOWN | ||
} | ||
directive @auth( | ||
requires: Role = ADMIN, | ||
) on OBJECT | FIELD_DEFINITION | ENUM | ||
type Book @auth(requires: ADMIN) { | ||
title: String | ||
author: String | ||
} | ||
type Query { | ||
me: User | ||
books: [Book] @auth(requires: ADMIN) | ||
} | ||
``` | ||
#### Make filtered schema | ||
```js | ||
import makeFilteredSchema, { schemaDirectivesToFilters } from 'graphql-introspection-filtering'; | ||
const schema = makeFilteredSchema( | ||
makeExecutableSchema({ | ||
typeDefs, | ||
resolvers, | ||
schemaDirectives | ||
}), | ||
schemaDirectivesToFilters(schemaDirectives) | ||
); | ||
``` | ||
#### AuthenticationDirective | ||
```js | ||
class AuthenticationDirective extends SchemaDirectiveVisitor { | ||
static RequiredRole = Symbol('RequiredRole'); | ||
_wrappedSymbol = Symbol('wrapped'); | ||
// filter introspection types | ||
static visitTypeIntrospection(field, _, __, context) { | ||
return AuthenticationDirective.isAccessible(field, context); | ||
} | ||
// filter introspection fields | ||
static visitFieldIntrospection(field, _, __, context) { | ||
return AuthenticationDirective.isAccessible(field, context); | ||
} | ||
// filter introspection directives | ||
static visitDirectiveIntrospection({ name }) { | ||
return name !== 'auth'; | ||
} | ||
// decide if user has access to the node | ||
static isAccessible(field, context) { | ||
const requiredAuthRole = field[AuthenticationDirective.RequiredRole]; | ||
if (requiredAuthRole) { | ||
if (!context || !context.user || !context.user.roles.includes(requiredAuthRole)) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
constructor(...args) { | ||
super(...args); | ||
this.ensureFieldWrapped = this.ensureFieldWrapped.bind(this); | ||
} | ||
ensureFieldWrapped(field) { | ||
if (field[this._wrappedSymbol]) return; | ||
field[this._wrappedSymbol] = true; | ||
const { resolve = defaultFieldResolver } = field; | ||
field.resolve = this.wrapField.call(this, resolve, field); | ||
} | ||
visitObject(obj) { | ||
this.ensureFieldWrapped(obj); | ||
obj[AuthenticationDirective.RequiredRole] = this.args.requires; | ||
} | ||
visitEnum(en) { | ||
this.ensureFieldWrapped(en); | ||
en[AuthenticationDirective.RequiredRole] = this.args.requires; | ||
} | ||
visitFieldDefinition(field) { | ||
this.ensureFieldWrapped(field); | ||
field[AuthenticationDirective.RequiredRole] = this.args.requires; | ||
} | ||
wrapField(resolve, field) { | ||
return async (root, args, context, info) => { | ||
if (!AuthenticationDirective.isAccessible(field, context)) { | ||
throw new Error('Not authorized!'); | ||
} | ||
return resolve.call(this, root, args, context, info); | ||
}; | ||
} | ||
} | ||
``` |
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
28776
1
177