Graphql-shield: Possible to protect Prisma-style nested mutations?

Created on 4 Feb 2019  路  5Comments  路  Source: maticzav/graphql-shield

With Prisma, I can write nested mutations like so:

gql mutation{ createPost(data: { body: "Hello World" tags: { create: [ {name: "cats"} ], } }){ body tags{ name } } }

Using Shield, I can set rules for createPost but any such rule bypasses all my rules on createTag. So basically if a user has permission to create a post, they can by extension create anything else that a post connects to. ie. It's impossible to allow a user to updatePosts without also allowing them to update any tag, user, comment etc connected to that post. I think this is an inherent limitation of graqhql middleware and Prisma--they are not aware of these nested mutations and thus do not hit any mutation middleware after the top level.

Am I correct or missing something? How do people handle the case where a user is connected to a post? Can anyone update that user who can update the post?

Thanks!

kinquestion

All 5 comments

Hey @nolandg 馃憢,

I wouldn't say this is a "limitation" of graphql-middleware and prisma because GraphQL does not support nested mutations as such. What prisma does is use the arguments of a function to change the nodes internally. In that sense, there are no "nested-mutations" only the Prisma API which handles connections in such a manner.

Considering this, the majority of the systems rely on unwrapped Prisma servers, if I name them so, which means that they change the schema to their needs and use prisma only as a delegation layer which happens to use GraphQL as well. I believe this might be connected to #113. So far, I haven't come across a meaningful example which would persuade me into implementing such functionality. As a note, I would say that exposing Prisma API is far too risky because you have no control over connect and create arguments, as you already mentioned, plus you usually only need about a fifth of all the exposed functionality.

Nevertheless, if you believe this is your only option, I would go about implementing it as an argument checker. Take a look at the example below.

const hasPermission = rule({ cache: 'strict' })(
  async (parent, args, ctx, info) => {
    if (args.data.tags.create !== undefined) {
      return isUserAuthenticated(ctx)
    }
    return true
  },
)

I hope this helps you solve your problem. 馃檪

@nolandg I have some idea for you. It just something I thought about yesterday so it may be complete bs, but why not share it :)

Solution: just use regex to cut some functionality from your prisma generated SDL before exposing it to your server. (If you'll need some of it later you can explicitly import/declare/whatever it.)

Prisma generated schemas have quite predictable names based on kind of relationships.

So first level input arguments look like this Name + (Update|Create|Delete)Input

input TaskUpdateInput {
  owners: UserUpdateManyWithoutTasksInput # captured
  candidate: CandidateUpdateOneWithoutTasksInput # captured
  title: String
  description: String
  dueAt: DateTime
}

So you can cut (e.g via String.prototype.replace()) all 'nested' inputs fields using regex like this. And now you have no nested fields :tada:

https://regexr.com/47rnh

/(?!input [A-Z][a-z]+ UpdateInput { \n .*)([a-z]+: [A-z][a-z]+(?:UpdateOne|UpdateMany)Without).*/mg

Or you can target your second and more nested level inputs to specifically disallow just e.g. deleteMany/delete

https://regexr.com/47rm4

/(?!input [A-Z][a-z]+Update(Many|One)Without[A-Z][a-z]+Input {\n.*)(delete|deleteMany): .*/mg
input TaskUpdateManyWithoutCandidateInput {
  create: [TaskCreateWithoutCandidateInput!]
  delete: [TaskWhereUniqueInput!] # captured
  connect: [TaskWhereUniqueInput!]
  disconnect: [TaskWhereUniqueInput!]
  update: [TaskUpdateWithWhereUniqueWithoutCandidateInput!]
  upsert: [TaskUpsertWithWhereUniqueWithoutCandidateInput!]
  deleteMany: [TaskScalarWhereInput!] # captured
  updateMany: [TaskUpdateManyWithWhereNestedInput!]
}

Those are just 5 minutes regexes, but with a bit of tinkering you can create some sort of config/builder/whitelister/validator for them. It's a bit hacky, but also - is it really? All of the cases are fairly predictable. Also you don't need to necessarily just cut the fields - you can modify them - and delegation to prisma via binding works with changed type names - as long as the shapes are compatible (or at least it worked few months ago^^).

Then you can use @maticzav idea to guard input arguments on fields you'll actually need in your schema :)

As example: I wrote tiny tool like that yesterday for a bit different thing (using regex for importing specific fields etc.) but it may give you idea how to implement this: https://github.com/vadistic/gql-import

@vadistic if you're interested in modifying and exposing a subset of a schema as a new schema I recommend you check out https://github.com/mitoai/gintonic

Thanks for the suggestions guys. I'm basically already doing what @maticzav suggests. My framework has its own schema that's used to generate Prisma config and bindings, graphql, and middleware for every query and mutation. In the middleware I whitelist every field based its schema type and permissions. Oh course this solution is tightly coupled to Prisma and depends on interpreting Prisma mutation args. I was wondering if anyone else had solved this in a more general way but I don't think that's possible.

Hey :wave:,

We saw you use our package. To keep our code as accessible as possible, we decided to open source it and are now helping others with their questions.

In a pursuit to continue our work, help us by donating to our collective! :heart:

https://opencollective.com/graphql-shield

Was this page helpful?
0 / 5 - 0 ratings

Related issues

devautor picture devautor  路  8Comments

FluorescentHallucinogen picture FluorescentHallucinogen  路  8Comments

jahudka picture jahudka  路  3Comments

BjoernRave picture BjoernRave  路  5Comments

devautor picture devautor  路  7Comments