graphql-shield icon indicating copy to clipboard operation
graphql-shield copied to clipboard

Documents are not populated when running `graphql-shield`, thus leading to `undefined` permission errors.

Open m-lyon opened this issue 1 year ago • 0 comments

Question about GraphQL Shield

I am using mongoose with graphql via graphql-compose-mongoose with a schema that has nested relational documents. To populate the document for a query I have provided relation methods via addRelation, which works in the absence of graphql-shield permissions, but encounters an _id undefined error when using permissions - presumably because the graphql-shield middleware runs before the documents can be populated?

I have provided a minimal working example below. My question is how can I get this to work?

import { Schema, Document, model, Types } from 'mongoose';
import { composeMongoose } from 'graphql-compose-mongoose';
import { SchemaComposer } from 'graphql-compose';
import { applyMiddleware } from 'graphql-middleware';
import { shield, allow } from 'graphql-shield';

// Mongoose models
export interface Ingredient extends Document {
    name: string;
}

const ingredientSchema = new Schema<Ingredient>({
    name: { type: String, required: true, unique: true },
});

export interface RecipeIngredient extends Document {
    ingredient: Types.ObjectId;
    quantity: number;
}
const recipeIngredientSchema = new Schema<RecipeIngredient>({
    ingredient: { type: Schema.Types.ObjectId, refPath: 'Ingredient', required: true },
    quantity: { type: Number, required: true },
});

export interface Recipe extends Document {
    title: string;
    ingredients: RecipeIngredient[];
}

const recipeSchema = new Schema<Recipe>({
    title: { type: String, required: true },
    ingredients: {
        type: [{ type: recipeIngredientSchema }],
        required: true,
    },
});

export const Ingredient = model<Ingredient>('Ingredient', ingredientSchema);
export const IngredientTC = composeMongoose(Ingredient);
export const RecipeIngredient = model<RecipeIngredient>('RecipeIngredient', recipeIngredientSchema);
export const RecipeIngredientTC = composeMongoose(RecipeIngredient);
export const Recipe = model<Recipe>('Recipe', recipeSchema);
export const RecipeTC = composeMongoose(Recipe);

RecipeIngredientTC.addRelation('ingredient', {
    resolver: () => IngredientTC.mongooseResolvers.findById(),
    prepareArgs: {
        _id: (source) => source.ingredient._id,
    },
    projection: { ingredient: true },
});

// GraphQL resolvers
export const IngredientQuery = {
    ingredientById: IngredientTC.mongooseResolvers.findById(),
    ingredientByIds: IngredientTC.mongooseResolvers.findByIds(),
    ingredientOne: IngredientTC.mongooseResolvers.findOne(),
    ingredientMany: IngredientTC.mongooseResolvers.findMany(),
};
export const RecipeQuery = {
    recipeById: RecipeTC.mongooseResolvers.findById(),
    recipeByIds: RecipeTC.mongooseResolvers.findByIds(),
    recipeOne: RecipeTC.mongooseResolvers.findOne(),
    recipeMany: RecipeTC.mongooseResolvers.findMany(),
};

// GraphQL shield
export const permissions = shield({ Query: { recipeById: allow } }, { allowExternalErrors: true });

const schemaComposer = new SchemaComposer();
schemaComposer.Query.addFields({
    ...IngredientQuery,
    ...RecipeQuery,
});

export const schema = applyMiddleware(schemaComposer.buildSchema(), permissions);
"""GraphQL Query"""
query ExampleQuery($recipeId: MongoID!) {
  recipeById(_id: $recipeId) {
    _id
    ingredients {
      _id
      quantity
      ingredient {
        _id
        name
      }
    }
  }
}

Here is the output for said query:

{
  "data": {
    "recipeById": {
      "_id": "655bf525063f734161299e59",
      "ingredients": [
        {
          "_id": "655bf525063f734161299e5a",
          "quantity": 1,
          "ingredient": null
        }
      ]
    }
  },
  "errors": [
    {
      "message": "Cannot read properties of undefined (reading '_id')",
      "locations": [
        {
          "line": 7,
          "column": 7
        }
      ],
      "path": [
        "recipeById",
        "ingredients",
        0,
        "ingredient"
      ],
      "extensions": {
        "code": "INTERNAL_SERVER_ERROR",
        "stacktrace": [
          "TypeError: Cannot read properties of undefined (reading '_id')",
          "    at _id (file:///graphql-shield-example/dist/schema.js:29:44)",
          "    at graphql-shield-example/node_modules/graphql-compose/lib/ObjectTypeComposer.js:1076:36",
          "    at Array.forEach (<anonymous>)",
          "    at resolve (graphql-shield-example/node_modules/graphql-compose/lib/ObjectTypeComposer.js:1075:25)",
          "    at file:///graphql-shield-example/node_modules/graphql-middleware/dist/applicator.mjs:5:112",
          "    at middleware (file:///graphql-shield-example/node_modules/graphql-shield/esm/generator.js:27:30)",
          "    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)"
        ]
      }
    },
  ]
}
  • [x] I have checked other questions and found none that matches mine.

m-lyon avatar Nov 21 '23 16:11 m-lyon