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

Replace merge-graphql-schemas with graphql-tools #1230

Closed
peterp opened this issue Sep 28, 2020 · 7 comments · Fixed by #1322
Closed

Replace merge-graphql-schemas with graphql-tools #1230

peterp opened this issue Sep 28, 2020 · 7 comments · Fixed by #1322

Comments

@peterp
Copy link
Contributor

peterp commented Sep 28, 2020

We're using merge-graphql-schemas which is now deprecated:

import { mergeTypes } from 'merge-graphql-schemas'

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.

@peterp
Copy link
Contributor Author

peterp commented Sep 30, 2020

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

cd package/api
yarn test:watch

@dthyresson
Copy link
Contributor

Note that by implementing this replacement, if directives work, then could use instead of graphql-shield to protect queries and mutations as described in #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],
});
});

@himankpathak
Copy link
Contributor

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

@thedavidprice
Copy link
Contributor

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: #1266

@himankpathak
Copy link
Contributor

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

@thedavidprice
Copy link
Contributor

@himankpathak yes, indeed!

Would you like me to assign this to you?

@himankpathak
Copy link
Contributor

Sure @thedavidprice , you can assign me the issue.

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

Successfully merging a pull request may close this issue.

4 participants