-
-
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) | |
} | |
} | |
}); |
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).
@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" :)
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: "[email protected]" }
. 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" } }
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.
@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!
This generally looks good. However, my problem is with nested data that a user may or may not have access to.
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!