Skip to content

Instantly share code, notes, and snippets.

@eyston
Last active November 5, 2015 04:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eyston/ace33b385f57aabc7807 to your computer and use it in GitHub Desktop.
Save eyston/ace33b385f57aabc7807 to your computer and use it in GitHub Desktop.
const hasRole = (role, next) => {
return (obj, args, ctx) => {
if (ctx.rootValue.user.hasRole(role)) {
next(obj, args, ctx);
} else {
return null;
}
}
}
const { query, variables } = request.params; // from the request
const user = db.getUser(request.session.userId); // from your session -- pretend it has user id or something!
graphql(schema, query, {user}, variables); // shove the user in the root value
const UserType = new GraphQLObjectType({
name: 'User',
fields: {
email: {
type: GraphQLString,
resolve: (user, _, {rootValue}) => {
// only the current user can see their email
// can imagine this checking admin roles and stuff
if (user.id === rootValue.user.id) {
return user.email;
} else {
// maybe you can throw to add an error to the response!
return null;
}
}
},
socialSecurityNumber: {
type: GraphQLString,
resolve: hasRole('admin', user => user.socialSecurityNumber)
}
}
});
@bostonou
Copy link

bostonou commented Nov 5, 2015

This generally looks good. However, my problem is with nested data that a user may or may not have access to.

(let [query {:project {:id :some-project-id
                       :files [{:id :my-file}
                               {:id :not-my-file}]}]
   (resolve ...))

So how would you write resolve so that a user with access to :some-project-id can get :my-file but not :not-my-file? Other than walking the whole tree of the query, not sure how you'd do it.

Also, thanks a bunch for the pointers!

@eyston
Copy link
Author

eyston commented Nov 5, 2015

The resolve is going to be per field. So really this could end up being done in three resolves:

  • project resolve: does the user have access to this project?
  • files resolve: in general I would imagine you'd have an existing method get-project-files-for-user that only returned a list of files the user has access to. I mean, if you were exposing this via any kind of existing API (http, etc) you'd have already written this method.
  • individual file resolve: does the user have access to the file?

In all of these cases I think the authorization check is independent of graphql. What I mean is the logic to determine if a user has access to a project is code that you probably already have and can share between all public ways you expose the data (http, graphql, etc).

@bostonou
Copy link

bostonou commented Nov 5, 2015

@eyston a resolve per field is another version of "walk the whole tree", but it's cool if that's part of the GraphQL implementation.

I come from the Clojure/Datomic world, and GraphQL-ish design is intriguing because we already use similar queries (e.g. pull syntax). In a perfect world, I could just pass query straight to Datomic after checking authorization on :project-id. However, I also need to authorize on children (e.g. :files). So I need to walk the query to find all ids that need to be authorized before running the query (or filter the results).

Having static queries would make the walking part more straightforward, but still not as easy as "authorize root to use query" :)

@eyston
Copy link
Author

eyston commented Nov 5, 2015

One last thing to remember: only scalar resolves are values that end up in the response.

Take the query:

query {
  user(id: 1) {
    name
  }
}

The resolve for user(id: 1) might query a database which returns {id: 1, name: "Erik", email: "eyston@gmail.com" }. This complex doesn't get copied to the response, it just gets handed to the next resolve function ... so your query response is currently:

{ "user": { } }

Then the resolve for name gets run. This resolve returns a value and since it is a scalar it gets added to the response:

{ "user": { "name": "Erik" } }

@eyston
Copy link
Author

eyston commented Nov 5, 2015

Yah, I've been tracking the om-next stuff. GraphQL is different in that it in fact is spec to walk the whole tree. This is actually what I wanted to get at in the last comment -- only the leave values matter. Every complex value which gets returned is simply the collection of its leaves.

With om-next the parser can return complex values meaning you do not necessarily walk the leaves. In this case your parser/read for :project would want to make sure the value it returns has been pre-filtered to the current user.

I haven't implemented a parser / done much with om-next but maybe the parser can be recursive? The Falcor router essentially does this. One route might handle :project and then defer to a different handler which handles :file or something.

@eyston
Copy link
Author

eyston commented Nov 5, 2015

@bostonou I can't stop:

https://www.youtube.com/watch?v=7lm3K8zVOdY

This was a talk from clojure/conj 2014 where a bank actually filters a datomic database to only include attributes a user can see. This means any query can be run safely as the only attribute values which can possibly be returned are authorized.

I have _zero_ idea if this is a good / bad idea, but definitely neato. And cognitect has included them in literature so maybe its a great idea, I dunno!

http://blog.cognitect.com/blog/2015/9/14/nubank

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment