const { SchemaDirectiveVisitor } = require('graphql-tools') const { defaultFieldResolver } = require('graphql') const _ = require('lodash') class AuthDirective extends SchemaDirectiveVisitor { visitObject(type) { this.ensureFieldsWrapped(type) type._requiredAuthScopes = this.args.requires } // Visitor methods for nested types like fields and arguments // also receive a details object that provides information about // the parent and grandparent types. visitFieldDefinition(field, details) { this.ensureFieldsWrapped(details.objectType) field._requiredAuthScopes = this.args.requires } visitArgumentDefinition(argument, details) { this.ensureFieldsWrapped(details.objectType) argument._requiredAuthScopes = this.args.requires } ensureFieldsWrapped(objectType) { // Mark the GraphQLObjectType object to avoid re-wrapping: if (objectType._authFieldsWrapped) return objectType._authFieldsWrapped = true const fields = objectType.getFields() Object.keys(fields).forEach(fieldName => { const field = fields[fieldName] const { resolve = defaultFieldResolver } = field field.resolve = async function (...args) { // Get the required scopes from the field first, falling back // to the objectType if no scopes is required by the field: const requiredScopes = field._requiredAuthScopes || objectType._requiredAuthScopes if (!requiredScopes) { return resolve.apply(this, args) } const context = args[2] if (!context.req.user) { throw new Error('Unauthorized') } if (!_.some(context.req.user.permissions, pm => _.includes(requiredScopes, pm))) { throw new Error('Forbidden') } return resolve.apply(this, args) } }) } } module.exports = AuthDirective