Redwood: Replace merge-graphql-schemas with graphql-tools

Created on 28 Sep 2020  路  7Comments  路  Source: redwoodjs/redwood

We're using merge-graphql-schemas which is now deprecated: https://github.com/redwoodjs/redwood/blob/e5574f9eba1578f1dc03ba30fa374e6a5083de3a/packages/api/src/makeMergedSchema/makeMergedSchema.ts#L7

Let's rather use graphql-tools/merge package:
https://www.graphql-tools.com/docs/migration-from-merge-graphql-schemas/

I think this may be required for directives to work properly.

hacktoberfest api

Most helpful comment

Sure @thedavidprice , you can assign me the issue.

All 7 comments

We've got tests for this that'll help with the conversion:

cd package/api
yarn test:watch

Note that by implementing this replacement, if directives work, then could use instead of graphql-shield to protect queries and mutations as described in https://github.com/redwoodjs/redwood/issues/965 and explained in https://www.apollographql.com/docs/apollo-server/schema/creating-directives/#enforcing-access-permissions

directive @auth(
  requires: Role = ADMIN,
) on OBJECT | FIELD_DEFINITION

enum Role {
  ADMIN
  REVIEWER
  USER
  UNKNOWN
}

type User @auth(requires: USER) {
  name: String
  banned: Boolean @auth(requires: ADMIN)
  canPost: Boolean @auth(requires: REVIEWER)
}

and also in https://www.graphql-tools.com/docs/schema-directives/#enforcing-access-permissions

function authDirective(directiveName: string, getUserFn: (token: string) => { hasRole: (role: string) => boolean} ) {
  const typeDirectiveArgumentMaps: Record<string, any> = {};
  return {
    authDirectiveTypeDefs: `directive @${directiveName}(
      requires: Role = ADMIN,
    ) on OBJECT | FIELD_DEFINITION

    enum Role {
      ADMIN
      REVIEWER
      USER
      UNKNOWN
    }`,
    authDirectiveTransformer: (schema: GraphQLSchema) => mapSchema(schema, {
      [MapperKind.TYPE]: (type) => {
        const typeDirectives = getDirectives(schema, type);
        typeDirectiveArgumentMaps[type.name] = typeDirectives[directiveName];
        return undefined;
      },
      [MapperKind.OBJECT_FIELD]: (fieldConfig, _fieldName, typeName) => {
        const fieldDirectives = getDirectives(schema, fieldConfig);
        const directiveArgumentMap = fieldDirectives[directiveName] ?? typeDirectiveArgumentMaps[typeName];
        if (directiveArgumentMap) {
          const { requires } = directiveArgumentMap;
          if (requires) {
            const { resolve = defaultFieldResolver } = fieldConfig;
            fieldConfig.resolve = function (source, args, context, info) {
              const user = getUserFn(context.headers.authToken);
              if (!user.hasRole(requires)) {
                throw new Error('not authorized');
              }
              return resolve(source, args, context, info);
            }
            return fieldConfig;
          }
        }
      }
    })
  };
};

function getUser(token: string) {
  const roles = ['UNKNOWN', 'USER', 'REVIEWER', 'ADMIN'];
  return {
    hasRole: (role: string) => {
      const tokenIndex = roles.indexOf(token);
      const roleIndex = roles.indexOf(role);
      return roleIndex >= 0 && tokenIndex >= roleIndex;
    },
  };
}

const { authDirectiveTypeDefs, authDirectiveTransformer } = authDirective('auth', getUser);

const schema = makeExecutableSchema({
  typeDefs: [authDirectiveTypeDefs, `
    type User @auth(requires: USER) {
      name: String
      banned: Boolean @auth(requires: ADMIN)
      canPost: Boolean @auth(requires: REVIEWER)
    }

    type Query {
      users: [User]
    }
  `],
  resolvers: {
    Query: {
      users() {
        return [
          {
            banned: true,
            canPost: false,
            name: 'Ben',
          },
        ];
      },
    },
  },
  schemaTransforms: [authDirectiveTransformer],
});
});

Hey @peterp, I would like to work on this issue. Is this issue up for taking?

Hi @himankpathak! Would be great to have your help!

Peter is on vacation for the next week. Since he's the lead on this part of the code, we won't be able to have him Review and Merge until the week after he's back (still in time for Hacktoberfest). But if you want to get started and open a PR, both I and @dthyresson could likely guide you on your way.

Does that sound good to you? Let me know and I'll assign this to you.


Lastly, if you haven't seen it yet here's some helping getting started information: https://github.com/redwoodjs/redwood/issues/1266

Thanks @thedavidprice,
So I have to follow the following guide https://www.graphql-tools.com/docs/migration-from-merge-graphql-schemas/ right?

@himankpathak yes, indeed!

Would you like me to assign this to you?

Sure @thedavidprice , you can assign me the issue.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jeliasson picture jeliasson  路  3Comments

jtoar picture jtoar  路  4Comments

hemildesai picture hemildesai  路  4Comments

wispyco picture wispyco  路  3Comments

cannikin picture cannikin  路  3Comments