Graphql-shield: [Feature request] Allow decentralizing the definition of the rules

Created on 18 Sep 2018  路  5Comments  路  Source: maticzav/graphql-shield

Hello,

Feature request

Is your feature request related to a problem? Please describe

I want to develop my server's typeDefs/resolvers in a modular way. In order to do it, I need a way to collocate shield's rules with my resolvers.

Describe the solution you'd like

I'd like to leverage the fact that we can declare a resolver using an object with the resolve key to add extra informations for my middlewares stack :

const resolvers = {
  Mutation: {
    createUser: {
      // pass rules using a special "permissions" key
      permissions: isAuthenticated,
      resolve: forwardTo('db')
    }
  }
}

Additional context

I already do this for my validation layer (big props to @JCMais for showing the way with his awesome post 馃憦 ) and it's working great so far :

const resolvers = {
  Mutation: {
    createUser: {
      // pass rules using a special "permissions" key
      permissions: isAuthenticated,
      // pass validation rules to my yup-middleware
      validation: (_, __, { db }) =>
        update('data.username', f =>
          f.unique(async username => !(await db.exists.User({ username })))
        )
      resolve: forwardTo('db')
    }
  }
}

Implementation

@maticzav gave me hints about collecting all the rules from the schema using something like https://github.com/graphql-binding/graphql-binding/blob/master/src/fragmentReplacements.ts

Here you can still declare shield's the current way by passing a map of your rules, but you can also overwrite them or define new ones using a special key inside your resolvers definitions (I pick 'permissions' by default, but it could be 'shield' as well, you can pick your own!)

const _ = require('lodash/fp')
const graphqlShield = require('graphql-shield')
const { middleware } = require('graphql-middleware')

function shield(initialPermissions = {}, options = {}) {
  const { permissionsKey = 'permissions', ...shieldOptions } = options
  return middleware(schema => {
    const permissions = _.merge(
      initialPermissions,
      collectPermissions(schema, permissionsKey)
    )
    return graphqlShield.shield(permissions, shieldOptions).generate(schema)
  })
}

function collectPermissions(schema, permissionsKey) {
  const resolvers = {
    Query: schema.getQueryType().getFields(),
    Mutation: schema.getMutationType().getFields()
  }
  let permissions = {}
  for (const typeName in resolvers) {
    const fieldResolvers = resolvers[typeName]
    for (const fieldName in fieldResolvers) {
      const fieldResolver = fieldResolvers[fieldName]
      if (fieldResolver[permissionsKey]) {
        permissions = _.set(
          [typeName, fieldName],
          fieldResolver[permissionsKey],
          permissions
        )
      }
    }
  }
  return permissions
}

module.exports = {
  ...graphqlShield,
  shield
}
kinfeature

Most helpful comment

Btw, about this topic, I've created a RFC on graphql-js for having a supported way to declare this extra metadata directly on each field definition: https://github.com/graphql/graphql-js/issues/1527

Would love some opinions there

All 5 comments

Hey @jgoux 馃憢,

I think yours is a fantastic idea! Being able to define rules next to your resolvers could open up a whole new set of possibilities. I believe this idea would best be best implemented as a side package, primarily to keep shield as unopinionated as possible - in a structure non-interrupting manner.

Furthermore, what I am curious about is how would you suggest the implementation of type- or schema-wide rules. These are rules that are applied to every field of a particular type or, as in the second case, to every single field in a schema?

Let me know what you think! 馃檪

how would you suggest the implementation of type- or schema-wide rules

I thought about type-wide rules and I didn't find a proper solution for them yet. We can't really declare them alongside the resolvers (at least using the regular syntax) because it's not a valid GraphQL typedef. For now you can still declare them in the centralized config object.

For the schema-wide rules, let's just keep them in the centralized configuration as they are global per se. 馃槃

I'll publish the current implementation as a side-package then. Do you have any idea for the name? It will be the exact same API as shield, with the additional permissionsKey in the option object.

Btw, about this topic, I've created a RFC on graphql-js for having a supported way to declare this extra metadata directly on each field definition: https://github.com/graphql/graphql-js/issues/1527

Would love some opinions there

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

The feature has been merge in graphql-js and graphql-tools recently, maybe a guide could be implemented here?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

donedgardo picture donedgardo  路  6Comments

artemzakharov picture artemzakharov  路  8Comments

nolandg picture nolandg  路  5Comments

creativiii picture creativiii  路  4Comments

devautor picture devautor  路  7Comments