question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

GraphQL Shield 2.0

See original GitHub issue

Where is graphql-shield headed

First, thanks for all the great feedback you’ve all given me so far. While listening to you and thinking about what I want and how I would like to use graphql-shield in my projects, I came to some conclusions, which will lead the second version of this package.

Whitelisting

The most requested feature and I, in my opinion, the most important one is whitelisting. Whitelisting, in comparison to blacklisting, requires all the exposed queries to be explicitly allowed in order to be used. Adoption of whitelisting also brings a lot easier stage separation as queries/mutations can be allowed in the development phase and restricted in production.

interface Options {
   debug: true // -> makes all the queries "available"
   cache: true
}

Nesting permissions

Currently, graphql-shield only supports nesting permissions using type-specific permissions, which get evaluated during execution chain.

const permissions = {
   Query: {
      me: authenticated
   },
   Me: {
      id: isMe,
      name: isMe,
      secret: isMe
   }
}

I think that the best approach to tackle this problem is by making possible lists of permissions.

const permissions = {
   Query: {
      me: [authenticated, isMe]
   },
}

As things get more complicated you might as well want to make some permissions optional (OR), which would require helper functions like oneOf and all to make it all possible.

const permissions = {
   Query: {
      posts: oneOf(isAdmin, isEditor, isOwner),
      oneTimeLatter: all(isAuthenticated, latterNotRead)
   }
}

This is also the solution I am aiming for.

Allow entire type permissions

Right now, you can only restrict certain fields and the entire types altogether. By introducing whitelisting instead of blacklisting this feature becomes essential as having to explicitly allow each field for each type is all too much work. In my opinion, making Type general permissions available would perfectly solve this problem.

const permissions = {
   Query: {
      me: auth
   },
   Me: allowAllFields
}

Debugging

I think that most of the functionality should be directed to developers during the development stage. By introducing the right tools to make permission handling as easy as possible, we can make an actual difference with graphql-shield.

Few ideas:

  • Summary of all permissions in debug mode.
  • Stage separation (as mentioned above) which would allow access to non-whitelisted queries to test and develop new permissions.

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Reactions:17
  • Comments:9

github_iconTop GitHub Comments

7reactions
a-typecommented, Feb 17, 2018

Looks like a good roadmap to me.

For what it’s worth, it’s not very hard to build your own permission composition on top of what you already have. I don’t think it’s really necessary to include a new resolver format like an array. Here’s what I’m working with so far (very WIP, just whipped this up in the last couple hours as I adopt this library):

const { get } = require('lodash');

const hasScope = (scopeName) => 
  (_parent, _args, ctx, _info) => get(ctx, 'user.scopes', []).includes(scopeName);

const belongsToMe = (typeName) => (_parent, args, ctx, _info) => {
  const id = args.id;
  const userId = ctx.user.id;
  return ctx.db.exists[typeName]({ where: { id, user: { id: userId } } });
};

const all = (...verifiers) => (parent, args, ctx, info) => {
  for (verifier of verifiers) {
    if (!verifier(parent, args, ctx, info)) {
      return false;
    }
  }
  return true;
};

const any = (...verifiers) => (parent, args, ctx, info) => {
  for (verifiers of verifiers) {
    if (verifier(parent, args, ctx, info)) {
      return true;
    }
  }
  return false;
};

module.exports = {
  hasScope,
  belongsToMe,
  all,
  any,
};

Usage has been a breeze:

const { hasScope, belongsToMe, all } = require('./generic');

module.exports = {
  Query: {
    user: hasScope('readUser'),
    users: hasScope('readUsers'),
    timeline: hasScope('readTimeline'),
    timelines: hasScope('readTimeline'),
    event: hasScope('readEvent'),
    events: hasScope('readEvent'),
    file: hasScope('readFile'),
    me: hasScope('me'),
  },

  Mutation: {
    updateMe: hasScope('updateMe'),
    createTimeline: hasScope('createTimeline'),
    updateTimeline: all(
      hasScope('updateMyTimeline'),
      belongsToMe('Timeline'), 
    ),
    deleteTimeline: all(
      hasScope('deleteMyTimeline'),
      belongsToMe('Timeline'), 
    ),
    createEvent: hasScope('createEvent'),
    updateEvent: all(
      hasScope('updateMyEvent'),
      belongsToMe('Event'),
    ),
    deleteEvent: all(
      hasScope('deleteMyEvent'),
      belongsToMe('Event'), 
    ),
    deleteFile: all(
      hasScope('deleteMyFile'),
      belongsToMe('File'),
    ),
    updateFile: all(
      hasScope('updateMyFile'),
      belongsToMe('File'),
    ),
    login: () => true,
    signup: () => true,
  },
};

all and any are powerful enough for my needs. If I wanted to create a new Admin role with a updateAnyTimeline scope, for instance, I could just nest the existing scopes in an any and write in the logic for that scope as another all block.

When my user authenticates, I load their scopes into the JWT token. That makes things super simple so far.

4reactions
a-typecommented, Feb 22, 2018

Being able to provide more detail would be great for testing and debugging. I think setError as described, though, represents a side-effect in an otherwise functional pattern.

Another suggestion: allow users to return an Error instance instead of false, or allow them to return a more detailed object with a field to indicate permission status, and a description of the problem to pass to the user ({ failed: true, message: 'Please contact your account administrator to change your payment details', data: { /* etc */ } })

Read more comments on GitHub >

github_iconTop Results From Across the Web

graphql-shield
GraphQL Shield helps you create permission layer for your application. Using intuitive rule-API, you'll gain the power of shield engine on every ...
Read more >
GraphQL Shield 2.0 · Issue #6
Currently, graphql-shield only supports nesting permissions using type-specific permissions, which get evaluated during execution chain. const ...
Read more >
Home – GraphQL Shield
GraphQL Shield Documentation. ... Shield lets you create a handful of rules and compose them into meaningful structures using logical operators.
Read more >
Graphql shield example
Graphql shield example. 1. Embed Fork Create Sandbox Sign in. Sandbox Info. Graphql shield example. 1. 1.6k. 9. IsaaXIsaaX. Forked FromGraphql shield ......
Read more >
pothos-examples/graphql-shield - NPM Package Overview
This example shows how you can create a simple plugin to apply graphql shield rules to your schema. Version: 2.2.15 was published by...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found