I'm wondering if it's possible to return different result for different users.
for example, i want to only permit users to see the posts they've created themselves. Should this be managed and can this be managed by graphql-shield?
Many thanks.
Hey hey @viztor 馃憢,
Great question. Would you mind telling me if you're using Prisma by chance? This way I can fine grain the answer.
@maticzav thank you for the response.
Yeah I'm happily embracing Prisma at the moment!
Perfect, I longed for that answer.
Let me start with the fact that graphql-shield provides different caching methods for different execution mechanisms. If your rule relies only on context of your resolver, say if a user is authenticated, you could use contextual cache. If you were not to use any cache at all, you could use no_cache option; and if your rule relies on data in arguments or parent, you should use strict cache.
Your case is a strict cache case. Your rules rely on the data from arguments since you want to limit the access to specific data. Let's make a quick checklist of things we need;
id for example.user and node.exists query.If you've ever used Graphcool before, exists comes in as a replacement for the permission system. You can read more about it here https://www.prisma.io/docs/reference/prisma-bindings/api-tho2eigeeh#exists.
Anyway, the core idea behind the exists query is; if a node with some filter exists it evaluates to true, otherwise it returns `false.
Furthermore, you have two options of how to tackle your problem. The first one is to add id argument to every field you wish to limit access to. This way, you would check whether a certain node, let's say Book for example, exists and it's owner happens to be the current user.
type Query {
book(id: ID!): Book
}
const isOwnerOfBook = rule({ cache: 'strict' })(async (parent, { id }, context, info) => {
const userId = getUserId(context)
return context.db.exists.Book({
id,
owner: { id: userId }
})
})
const permissions = shield({
book: isOwnerOfBook
})
The second option you have is to check the id of a particular field using parent property. parent provides a way of telling about your node. Let's make another hypothetical situation; let's say we are making a social network and we have a Profile type. Profile type also happens to have email information which is extremely sensitive, and we only want to show it to the owner himself. Now, people can come to Profile from various paths, feed with stories be one of them. If we were to try and use the above approach, we would find out that we have no way of providing the id through arguments. Because of that, we make use of parent.
type Query {
feed: [Stories!]!
}
type Story {
id: ID!
message: String!
creator: Profile!
}
type Profile {
id: ID!
name: String!
email: String
}
I'll assume you're using Apollo as a client and query
idby default, otherwise, check out #90 which should be released in a short while.
const isSelf = rule({ cache: 'strict' })(async ({ id }, arguments, context, info) => {
return context.userId === id
})
const permissions = shield({
Profile: {
email: isSelf
}
})
I hope this gives you a concrete starting point for your permission system. Let me know if anything seems unclear, and I'll help you out!
Thank you so much for the help! Only started trying GraphQL+prisma+Apollo few days ago, I was just evaluating permission control with shield or custom directive.
This looks amazing. 馃帀
Should've probably checked docs for graphql-middleware. Didn't realize that parent was referring to the result from parent query. (resolver function's parent was replaced with an _ in prisma docs)
Therefore, Is it correct to say that rule()() takes a function has the same signature as the resolver function in apollo and returns a Boolean indicating whether the field is accessible?
Maybe you want to add this example to the docs?
Furthermore, In a situation we only want to show, say the last four digit of phone number for verification purposes. Can it be done with shield? Guessing that is probably a right place for custom directives.
Can't say how much I appreciate the help!
Hey, excuse me for a bit delayed response.
Therefore, Is it correct to say that rule()() takes a function has the same signature as the resolver function in apollo and returns a Boolean indicating whether the field is accessible?
Indeed, that is the spec currently. I'll add it to the docs, thought it's clear enough from the signature itself.
Furthermore, In a situation we only want to show, say the last four digit of phone number for verification purposes. Can it be done with shield? Guessing that is probably a right place for custom directives.
I would say neither of them is the right approach to limit digits. I believe this is your business logic and probably one-field specific. Because of that, I would recommend you put the functionality in the resolver.
Even better, just got an idea now, make a new field named last4digits alongside other fields of the card. This way you reduce complexity and ease your work regarding security - make one "publicly" accessible and hide the others to account owners.
@maticzav
Yeah, it is indeed clear enough from the function signature. I got confused initially because other use-cases I've encountered before does not need parent, and shield is kind of the first package I run into that parent prove to be useful.
To use different field for masked data is really a brilliant 馃帀 idea! Looking forward to put that into practice.
Thanks!
Most helpful comment
@maticzav
Yeah, it is indeed clear enough from the function signature. I got confused initially because other use-cases I've encountered before does not need
parent, and shield is kind of the first package I run into thatparentprove to be useful.To use different field for masked data is really a brilliant 馃帀 idea! Looking forward to put that into practice.
Thanks!