GithubHelp home page GithubHelp logo

Comments (8)

maticzav avatar maticzav commented on April 27, 2024 7

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:

  1. We want to access Query.user field. GraphQL executes the resolver which returns a nested object obtained from Prisma.

  2. The result is forwarded to User type resolver, which has been auto-generated by graphql-yoga.

  3. 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 of User is forwarded to CreditCard resolver type.

  4. 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 created graphql-shield - to ease the creation of permission layer.

from graphql-shield.

maticzav avatar maticzav commented on April 27, 2024 5

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.

nolandg avatar nolandg commented on April 27, 2024 5

@maticzav Been reading a bunch of issues lately, thanks for your awesome clarity! you're good at explaining graphql.

from graphql-shield.

artemzakharov avatar artemzakharov commented on April 27, 2024 3

Thanks for the highly detailed answer, @maticzav!

I wanted to clarify a couple of things:

  1. My model doesn't actually look like that, that was more of an example 😄
  2. 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.

maticzav avatar maticzav commented on April 27, 2024

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.

artemzakharov avatar artemzakharov commented on April 27, 2024

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:
image
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.

maticzav avatar maticzav commented on April 27, 2024

@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.

colinhacks avatar colinhacks commented on April 27, 2024

@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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.