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.
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.
Most helpful comment
Sure @thedavidprice , you can assign me the issue.