I just updated my example / starter repo to include the new fangled directive permissions!
What’s that?
You decorate your schema with custom directives, e.g.
directive @hasRole(roles: [String]) on QUERY | FIELD | MUTATION
directive @isOwner(type: String) on QUERY | MUTATION
directive @isAuthenticated on QUERY | FIELD | MUTATION
directive @isOwnerOrHasRole(type: String, roles: [String]) on QUERY | MUTATION
type Query {
feed: [Post!]!
drafts: [Post!]! @isAuthenticated
post(id: ID!): Post @isOwnerOrHasRole(type: "Post", roles: ["ADMIN"])
me: User @isAuthenticated
}
And then you have a set of “directive resolvers” to implement the logic! e.g.
const directiveResolvers = {
...
hasRole: (next, source, { roles }, ctx) => {
const { role } = isLoggedIn(ctx)
if (roles.includes(role)) {
return next()
}
throw new Error(`Unauthorized, incorrect role`)
}
}
How do we implement this in Yoga? Using graphql-tools
and graphql-import
… Simple!
const schema = makeExecutableSchema({
typeDefs: importSchema("./src/schema.graphql"),
resolvers,
directiveResolvers
})
const server = new GraphQLServer({
schema,
context: req => ({
...req,
db
})
})
The brilliance is its flexibility. Need to get more granular?
directive @hasScope(scopes: [String]) on QUERY | FIELD | MUTATION
type Mutation {
createProduct(input: CreateProductInput): CreateProductPayload @hasScope(scopes: ["create:product"])
}
And you can chain them (but they are all ANDs, not ORs in a chain)
type Mutation {
updatePost(...): Post @isOwner @hasRole(roles: ["ADMIN"])
}
And for the mic drop, take a look at my Query resolvers now – complete with full authentication and permissions… (drafts
, post
, and me
are protected… but you don’t have to clutter your resolvers with the permissions boilerplate any longer!)
const resolvers = {
Query: {
feed(parent, args, ctx, info) {
return ctx.db.query.posts({ where: { isPublished: true } }, info)
},
drafts(parent, args, ctx, info) {
return ctx.db.query.posts(
{ where: { isPublished: false, user: { id: ctxUser(ctx).id } } },
info
)
},
post(parent, { id }, ctx, info) {
return ctx.db.query.post({ where: { id } }, info)
},
me(parent, args, ctx, info) {
return ctx.db.query.user({ where: { id: ctxUser(ctx).id } }, info)
}
},
...
I’ve been playing around with this all today and love it. Hopefully we can get a discussion going!