Graphback: Failure to map enum values in directives causing a "Generation failed: TypeError: JSON cannot represent value: JILL_MANAGER"

Created on 16 Jun 2020  Â·  14Comments  Â·  Source: aerogear/graphback

This seems to have started happening out of nowhere, and it happens on alpha 1, 2, 3 and 4.

I've got the following in my schema:

directive @read(roles: [AUTH_ROLE!]!) on FIELD_DEFINITION

type Query {
    twilioAccessToken: String!
    authenticatedUser: Users!
    inBusinessHours: Boolean!
    twilioChildCallSid(parentCallSid: String!): String!
    highLevelStatses(startDate: String!, endDate: String!): HighLevelStatses! @read(roles: [JILL_MANAGER, JILL_BUSINESS_DEVELOPMENT, JILL_EXECUTIVE])
    jillHighLevelStatses(jillId: ID!, startDate: String!, endDate: String!): JillHighLevelStatses! @read(roles: [JILL_MANAGER, JILL_BUSINESS_DEVELOPMENT, JILL_EXECUTIVE])
    teamHighLevelStatses(teamId: ID!, startDate: String!, endDate: String!): TeamHighLevelStatses! @read(roles: [JILL_MANAGER, JILL_BUSINESS_DEVELOPMENT, JILL_EXECUTIVE])
    callsForJillStats(jillId: ID!, startDate: String!, endDate: String!): [Calls!]! @read(roles: [JILL_MANAGER, JILL_BUSINESS_DEVELOPMENT, JILL_EXECUTIVE])
    billingReportCompanyIds(forMonth: String!): [ID!]!
    jillAnswerAttemptIds(jillId: ID!, startDate: String!, endDate: String!): [ID!]!
    incomingQueueForIntervalIds(startDate: String!, endDate: String!, incomingCallTypes: [INCOMING_CALL_TYPE!]!, podIds: [ID!]!): [ID!]!
}

enum AUTH_ROLE {
    ANONYMOUS
    NO_ROLE
    PRIVATE
    ANYONE
    AUTHENTICATED
    ENTITY_OWNER
    JILL
    JILL_OUTBOUND
    JILL_MANAGER
    JILL_BUSINESS_DEVELOPMENT
    JILL_EXECUTIVE
    COMPANY_EMPLOYEE
    COMPANY_OWNER
}

And when I run graphback generate I get Generation failed: TypeError: JSON cannot represent value: JILL_MANAGER. As far as I know, this wasn't happening until I upgraded to alpha 4. But I then downgraded to different alpha versions, and it kept repeating itself.

Most helpful comment

All 14 comments

Yep.. This looks like issue outside graphback but related to graphback dependencies (mainly graphql-compose).

Minimal reproduction:

directive @read(roles: [AUTH_ROLE!]!) on FIELD_DEFINITION

enum AUTH_ROLE {
    ANONYMOUS
}

type Query {
    highLevelStatses(startDate: String!, endDate: String!): HighLevelStatses! @read(roles: [JILL_MANAGER, JILL_BUSINESS_DEVELOPMENT, JILL_EXECUTIVE])
}

We need to check if we can

Offtopic,.

On the auth side, it is not good design to have auth roles tied to graphql schema. It is much better to keep roles in database tables.

In https://github.com/aerogear/keycloak-connect-graphql we had exactly the same thing - schema driven auth - but it is much more secure and simpler to manage auth rules in the code.

On the auth side, it is not good design to have auth roles tied to graphql schema. It is much better to keep roles in database tables.

Do you have more information on why this is not a good design? I've studied up on declarative permission with GraphQL quite a bit, and this seems like a good solution, but I'm open to understanding more why it might not be ideal.

It is binding instances of the roles into static compile time values of enum.
Usually roles are configured on some server like keycloak or part of the app database.
It is exposing role information to the client etc.

This is how we configure this ourselves - separate role file https://github.com/aerogear/datasync-starter/blob/master/server/src/config/auth.ts

We planning to add ownership type authz soon as well. Generally things like above will work for startup, but none of the business will put their Auth rules into API definitions that can be introspected or leaked.

Also directives are pretty bad to reflect some complex ACL syntax - this really begs to be code not schema

I see, though the developer experience of having declarative permissions in the schema is fantastic. I wonder if there is a way to get the best of both worlds, the permissions defined with the types in the schema, but hidden from the client. I'm not sure why this would be insecure. AWS AppSync even has schema directives for permissions: https://aws.amazon.com/blogs/mobile/using-multiple-authorization-types-with-aws-appsync-graphql-apis/

Looking more into it, seems like AWS is very comfortable with schema directives for permissions: https://docs.amplify.aws/cli/graphql-transformer/directives#auth

Thanks for highlighting this issue @lastmjs!

I tried it out and it appears to be an issue with graphql-compose.

I've create a bug report https://github.com/graphql-compose/graphql-compose/issues/262 with them so hopefully this will be resolved soon!

Amazing @nodkz - thank you!

@nodkz thanks for the fix. I've added a test case for this #1844 and it is failing with.

GraphQLError: Argument "roles" has invalid value ["ANONYMOUS"].

This happens when the schema is being printed using the following code

const schemaComposer = new SchemaComposer(schemaOrSDL)
schemaComposer.toSDL({ exclude: ['String', 'ID', 'Boolean', 'Float', 'Int'] })

I've just extended this test suite with toSDL check and all works correctly https://github.com/graphql-compose/graphql-compose/blob/master/src/__tests__/github_issues/262-test.js#L28-L53

Sorry, but I don't have enough free time to figure it out in your repo. @machi1990, Сan you extend test suite with your broken test case? Only after this, I can fix it.

Tnx.

Hi @nodkz thanks for the quick check.

I believe we have the same schema but this was more of missing descriptions / steps to reproduce what I was seeing. I am sorry for that.

This series of steps (taken from your test case), sum the issue up:

    const inputSchema = `
    directive @auth(permissions: [CrudPermissions]) on OBJECT | FIELD_DEFINITION

    """ @model """
    type Note {
      id: ID!
      title: String! @auth(permissions: [CREATE, READ])
    }

    enum CrudPermissions {
      CREATE
      READ
      UPDATE
      DELETE
    }
  `;  

  const composer = new SchemaComposer(inputSchema);
  const processedSchema = composer.buildSchema(); 
  const printedProcessedSchema = composer.toSDL({
    include: ['Note'],
    exclude: ['String', 'ID', 'Boolean', 'Float', 'Int'],
    omitDescriptions: true,
   });

  // at later stage
  const thisWorks = new SchemaComposer(processedSchema);

  const thisDoesNotWork = new SchemaComposer(printedProcessedSchema);

The error is

GraphQLError: Argument "permissions" has invalid value ["CREATE", "READ"]

@machi1990 thanks for test case. It fixed in 7.19.4

The problem was in printing SDL for Enum AST values in directive arguments:

UPD diff:

- title: String! @auth(permissions: ["CREATE", "READ"])
+ title: String! @auth(permissions: [CREATE, READ])

It was quite difficult to find and fix – https://github.com/graphql-compose/graphql-compose/commit/86765ffeaf923e6b6ca7d3e66ef0a0017c2e7d0a

Thank you @nodkz

@machi1990 thanks for test case. It fixed in 7.19.4

The problem was in printing SDL for Enum AST values in directive arguments:

- title: String! @auth(permissions: [CREATE, READ])
+ title: String! @auth(permissions: ["CREATE", "READ"])

It was quite difficult to find and fix – graphql-compose/graphql-compose@86765ff

Hi @nodkz thank you for the back to back quick fixes. Now my test case #1844 passes too.

@lastmjs we've dropped another 0.16.0-alpha3 which includes a fix for this issue. Mind checking if this release effectively fix the issue for you? Thanks!

Seems to work! Thanks!

Was this page helpful?
0 / 5 - 0 ratings