Comments (8)
Hey @artemzakharov 👋, I finally have some time to reply to your question! Here's the idea;
GraphQL works recursively. I suppose you are using Prisma as a database which often abstracts the query process to the point where it seems like responses are not processed on your server thoroughly. As you might have guessed from this, they still are - all of them!
If you have some spare time, I highly recommend reading this article because it very well explains the idea behind GraphQL execution.
Back to the point. Let's assume that your data model looks similar to this;
type User {
name: String!
age: Int!
creditCard: CreditCard!
}
type CreditCard {
number: String!
securityCode: String!
}
and that our schema is similar to the following one;
type Query {
user(id: ID!): User
}
Now, when you query the user, you first ask Prisma for a user with such and such id
. All good! We also provide the info
object with all the fields we want to access, and Prisma recursively obtains them for us. Up to this point, it seems like we have no direct control over the return values. The following part is the crucial one.
GraphQL (GraphQL Yoga + GraphQL Tools, in your case) define resolvers for every single field in our schema, even if we haven't explicitly told it to do so. Therefore, Query execution looks like this:
-
We want to access
Query.user
field. GraphQL executes the resolver which returns a nested object obtained from Prisma. -
The result is forwarded to
User
type resolver, which has been auto-generated bygraphql-yoga
. -
Each field that we have forwarded is also resolved by resolvers in
User
type and forwarded on until we reach final - scalar level. NOTE:creditCard
portion ofUser
is forwarded toCreditCard
resolver type. -
All of the values are composed together into a single response.
Finally, the solution! 🎉
You might have guessed where this was going before, otherwise, here's the idea. Instead of wrapping and modifying info
object, we just apply rules
to subfields of the nested types. In a case of the credit card, you could do something like this:
const permissions = shield({
Query: {
user: allow,
},
User: {
name: allow,
age: allow,
creditCard: deny
}
})
// or
const permissions = shield({
Query: {
user: allow,
},
CreditCard: {
securityCode: deny
}
})
Besides allow
and deny
, you could have used a more complex rule, such as isAdmin
or something similar for example, and only partially limit the access.
I agree that relations
are in a way still lost due to this approach. Nevertheless, such approach prevents many edge cases and reduces the number of tests needed for a genuinely secure codebase.
I hope this solves your situation at least to a certain degree or sparks some ideas in your mind. In any case, let me know! 🙂
PS.: If you are using Prisma, I highly recommend you copy all the generated types to your schema and remove the fields that shouldn't be exposed to the client. I would say this is one of the most overlooked security issues when using Prisma. Your server schema doesn't need to match the generated one, not even on the type level. GraphQL should figure all of this by itself out of the box!
I would also like to stress, that in a well-structured project,
graphql-shield
should become entirely obsolete. All of its functionality can be achieved using schema manipulation. Nevertheless, the usual approach is far from ideal and rather hasty. That's also the reason why I createdgraphql-shield
- to ease the creation of permission layer.
from graphql-shield.
Hey @colinmcd94, 👋
Sorry for the confusion. What I had in mind with "losing relations" is that there's no "direct" connection between values. Every field gets resolved on its own in a way.
A follow up to your second point; GraphQL always finds a way to stay true to your schema. Therefore, I wouldn't say permission could break the contract. It just changes the result.
Is that what you had in mind? 🙂
from graphql-shield.
@maticzav Been reading a bunch of issues lately, thanks for your awesome clarity! you're good at explaining graphql.
from graphql-shield.
Thanks for the highly detailed answer, @maticzav!
I wanted to clarify a couple of things:
- My model doesn't actually look like that, that was more of an example 😄
- I'm not actually modifying the
info
object in any way - I'm only interpreting it to see what information is being requested, and then comparing to a predefined list of allowed fields
Looking at your approach, however, it almost seems like an inversion of my way; instead of defining a template for a query - something like, in written form, "If you are running a User query, and are an Admin, you can access fields x, y, and z" - you instead say "if your query is asking for field x, you must satisfy criteria a, b, and c". It's a very interesting comparison, and I'm not sure which is better, or more applicable in more situations. I think mine is definitely simpler to implement, but I can see some future issues with scaling if the number of queries and mutations grows substantially while the number of types remains relatively fixed.
Definitely something to think about some more 😄
from graphql-shield.
Hey @artemzakharov 👋!
First, thank you for such a warm feedback. It's one of the things that drive me when making open source projects.
I think your question is very relevant. Therefore I think we should consider posting it in Prisma Forum as well!
It would be beneficial if you could provide your schema as well, as we can better understand your issue with such context.
Could you provide that? 🙂
from graphql-shield.
Hi @maticzav,
Unfortunately I can't provide my full schema since it's (ironically) a proprietary project that will have to deal with issues just like the one above, and I don't want to compromise security, but I would be happy to post snippets to clarify any questions about why I took the approach that I did.
I also can't exactly post the full schema even if I wanted to, because I don't have one per se... it's broken down by query/mutation into individual files, and assembled at runtime for a more modular development experience 🙂
My organizational structure looks like this:
And the GraphQL schema for this particular mutation looks like this:
type Mutation {
createCollectionAsPartner(
collectionCreateInput: CollectionCreateInput!
): Boolean
}
As you can see, I'm a big fan of leveraging types generated by Prisma - like CollectionCreateInput
- over fixed inputs - like (name: string, userId: string, etc...)
- which is probably where the concern to regulate what the shape of these inputs can be came from.
I wouldn't mind crossposting this to the official Prisma forum, but I'm not sure if I would have the time to oversee it. I'll consider doing it later this week, I just wanted to post it here first since it's a more thematic place to do so, but I'm glad (or not? 😅 ) to hear that this is in fact a real issue.
from graphql-shield.
@artemzakharov I see! I am currently on a short vacation and will be posting an in detail answer to your question in a couple of days.
Hope it's not urgent! 🙂
from graphql-shield.
@maticzav Thanks for the great explanation. I was with you right up until "relations are in a way still lost due to this approach". I'm not sure I understand how/why this is the case.
If I'm using Prisma and on my "application server" I implement resolvers for every field of every schema type (most of which just forward to the Prisma Docker container), relations remain intact, no?
Or are you just saying that a permissions scheme that blocks access to creditCard
essentially breaks the client-server contract defined by your schema?
from graphql-shield.
Related Issues (20)
- Execute root fields rules prior graphql execute phase
- Add the ability to use Fragments and post-execution rules with the single server/schema
- Add Typescript typings to rule args
- Add the ability to attach rules using GraphQL Directives
- CI: add codecov reports
- Provide a way to expose authorization metadata through the graphql schema
- GraphQL Shield Roadmap
- 7.6.4 ESM build broken HOT 5
- Documentation website down HOT 1
- How to use `if` condition in GraphQL Shield HOT 2
- Shield rules type generation based on schema HOT 1
- Feature request: wildcard functionality for field names
- Update [email protected] module to avoid @types/lodash and babel runtime in production deps
- fallbackError loses custom error types
- fallbackRule context information HOT 2
- Question: Is there a way to return objects on rules?
- ..
- Typo in the Docs
- Shield permissions only working properly with 'debug: true' HOT 1
- Performance -- every field wrapped is unnecessarily wrapped in a promise HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from graphql-shield.