Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
1 task done
m-lyon opened this issue Nov 21, 2023 · 3 comments

Comments

@m-lyon
Copy link

m-lyon commented Nov 21, 2023

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)"
        ]
      }
    },
  ]
}
  • I have checked other questions and found none that matches mine.
@benfliu
Copy link

benfliu commented Nov 7, 2024

@m-lyon Hey there - running into the same issue (also trying to use graphql-compose-mongoose with graphql-shield). Did you figure out how to make it work?

@m-lyon
Copy link
Author

m-lyon commented Nov 7, 2024

@benfliu I can't remember exactly my reasoning, but looking at the project I was trying to use this in: I ended up not using graphql-shield and instead implemented my own permission guards via GraphQL-Tools resolver composition, which was pretty straight forward and didn't run into this error.

@benfliu
Copy link

benfliu commented Nov 7, 2024

Got it! Thanks Matthew. Coming to a similar conclusion myself. Thought this library would save me time but looks like it might be more of a hassle than it's worth given this weird interaction

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants