Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature request] Allow decentralizing the definition of the rules #152

Closed
jgoux opened this issue Sep 18, 2018 · 5 comments
Closed

[Feature request] Allow decentralizing the definition of the rules #152

jgoux opened this issue Sep 18, 2018 · 5 comments
Labels
kind/feature A request for a new feature.

Comments

@jgoux
Copy link

jgoux commented Sep 18, 2018

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
}
@maticzav
Copy link
Owner

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! 🙂

@jgoux
Copy link
Author

jgoux commented Sep 20, 2018

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.

@JCMais
Copy link

JCMais commented Sep 20, 2018

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: graphql/graphql-js#1527

Would love some opinions there

@maticzav maticzav added the kind/feature A request for a new feature. label Oct 3, 2018
@stale
Copy link

stale bot commented Nov 17, 2018

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.

@stale stale bot added the stale label Nov 17, 2018
@stale stale bot closed this as completed Nov 27, 2018
@Sytten
Copy link
Contributor

Sytten commented Apr 17, 2020

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/feature A request for a new feature.
Projects
None yet
Development

No branches or pull requests

4 participants