Hi dear people, for approximately 30-40 hours I am...
# orm-help
g
Hi dear people, for approximately 30-40 hours I am looking for a way to realize some content-dependent authorization for mutations in a generic way. say, given are 3 Models Product -> Order -> User. and a nested mutation called createProduct. createProduct should authorize if user "James" is executing the mutation and the user record in the mutation argument holds his userID. It should not authorize, if there is a different userId inside in the user record. This should not be realized only with specific code only for the createProduct mutation. Why? Because there are also mutations like createCategory which in the end contain createProduct as sub mutation: Category -> Product -> Order -> User I tried so many things. Not possible mit Graphql-Shield or prisma middleware, or own typegraphl-middleware. Also I tried it with Graphql-Directives and Visitors. But this does only work for Queries - not for mutations, because ArgumentObjects or InputTypeObjects do not have reesolvers on their own. Researched so much articles and looked in the code, but it seems there is no easy way for it. In the moment I believe, the only method would be some ugly use of Magic field names "MAGIC_user_id" and each mutation should check in its arguments array, whether that MAGIC_user_id exists and compare, whether it is the current logged in id (which can be read from my context) Does anybody understand my problem or experienced similar and has an idea how to solve it?
r
@Georg Wagner đź‘‹ If I understood correctly, would it be possible to do this with a middleware resolver where you check if the user contains the user id? If not, could you provide a code-snippet if possible so that I understand it better?
g
Thanks.. sure. See, I have this mutation here. User James should not be allowed to execute it, because his user id ist "68... something". But here you can see by "connect" that he is inserting a different userId (he can manipulate his mutation query)
Copy code
mutation createProductMutation {
        createProduct(data: {
          name: "test50",
          orders:{
            create: {
              customer: {
                connect: {
                  id: "81c9cb84-6b9e-4b61-b067-33d83d813043"
                }  
              } 
            }
          }
        }) {
          id
        }
      }
So this should throw an authorization error, and not create. So my idea was to write some generic code, which hooks in all mutations which exist. But I know only a way to do it for this specific case. By using graphql directives. I created a directive "CreateOnlyByActiveUser".
Copy code
@TypeGraphQL.Mutation(_returns => Product, {
  nullable: false
})
@Directive('@CreateOnlyByActiveUser')
async createProduct(@TypeGraphQL.Ctx() ctx: any, @TypeGraphQL.Args() args: CreateProductArgs): Promise<Product> {
  return getPrismaFromContext(ctx).product.create(args);
}
So when the mutation is executed I am hooking in the resolver of that mutation and this code is executed. It works actually, but only for the above mentioned mutation, because the path is explicit bound to our example.
Copy code
field.resolve = async function (...args) {
// this code is executed when mutation createProduct is executed

  const [, inputArgs, context, info] = args;
  if (inputArgs.data.orders.create[0].customer.connect.id !== context.user.id) {
    console.log('You are not allowed to see');

    throw new AuthenticationError(
      `Only the active user is allowed to see create an order`,
    );
  } else {
    return resolve.apply(this, args);
  }
};
Imagine if I would not use connect in the end, but also create. Or maybe I am using only the lower level mutation createOrder.
mutation createProductMutation {
createOrder(data: { customer: { connect: { id: "81c9cb84-6b9e-4b61-b067-33d83d813043" } }, product:{ connect: { id: "23214214" } } }) { id } } Then I cannot reuse that directive, I have to adapt the path. My idea is to use a unique magic field name of "id" in object type "User" like "MAGIC_ID". Then my directive could search in the input args and validate it. But such design is bad and error prone. When working with authorization, we shouldn't work error-prone. So I was wondering whether there is a more elegant solution.
r
Wouldn’t this be possible with GraphQL shield? I can check the user via the token and the one creating should have the same
id
in
args
. Something like this.
This rule can be then applied to any Query/Mutation you want to by adding it in front of the respective Query/Mutation.
g
There were different attempt I tried with graphql-shield... I will retry again, thank you
r
Yes let me know what issue you’re facing when trying it with GraphQL Shield.
j
I also think it should be possible with graphql-shield https://graphql-shield.vercel.app/docs#example