Skip to content
This repository was archived by the owner on Apr 17, 2023. It is now read-only.

Commit 272415f

Browse files
authored
Adding example documenation for using apollo-server-express v3 directive transformers since this lib cant provide them as of now due to dependency issues. (#133)
1 parent 228ec2b commit 272415f

File tree

1 file changed

+110
-0
lines changed

1 file changed

+110
-0
lines changed

README.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,116 @@ Both the `@hasPermission` schema directive and the exported `hasPermission` func
200200
* If a single string is provided, it returns true if the keycloak user has a permission for requested resource and its scope, if the scope is provided.
201201
* If an array of strings is provided, it returns true if the keycloak user has **all** requested permissions.
202202

203+
## Apollo Server Express 3+ Support
204+
205+
`apollo-server-express@^3.x` no longer supports the `SchemaDirectiveVisitor` class and therefor prevents
206+
you from using the visitors of this library. They have adopted schema
207+
[transformers functions](https://www.apollographql.com/docs/apollo-server/schema/creating-directives/) that define behavior
208+
on the schema fields with the directives.
209+
210+
Remediating this is actually rather simple and gives you the option of adding a bit more authentication logic if needed,
211+
but will require some understanding of the inner workings of this library.
212+
213+
To make things easy, this is an example implementation of what the transformers may look like. (Note the validation of roles and permissions
214+
given to their respective directives):
215+
216+
```typescript
217+
import { defaultFieldResolver, GraphQLSchema } from 'graphql';
218+
import { getDirective, MapperKind, mapSchema } from '@graphql-tools/utils';
219+
import { auth, hasPermission, hasRole } from 'keycloak-connect-graphql';
220+
221+
const authDirectiveTransformer = (schema: GraphQLSchema, directiveName: string = 'auth') => {
222+
return mapSchema(schema, {
223+
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
224+
const authDirective = getDirective(schema, fieldConfig, directiveName)?.[0];
225+
if (authDirective) {
226+
const { resolve = defaultFieldResolver } = fieldConfig;
227+
fieldConfig.resolve = auth(resolve);
228+
}
229+
return fieldConfig;
230+
}
231+
});
232+
};
233+
234+
export const permissionDirectiveTransformer = (schema: GraphQLSchema, directiveName: string = 'hasPermission') => {
235+
return mapSchema(schema, {
236+
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
237+
const permissionDirective = getDirective(schema, fieldConfig, directiveName)?.[0];
238+
if (permissionDirective) {
239+
const { resolve = defaultFieldResolver } = fieldConfig;
240+
const keys = Object.keys(permissionDirective);
241+
let resources;
242+
if (keys.length === 1 && keys[0] === 'resources') {
243+
resources = permissionDirective[keys[0]];
244+
if (typeof resources === 'string') resources = [resources];
245+
if (Array.isArray(resources)) {
246+
resources = resources.map((val: any) => String(val));
247+
} else {
248+
throw new Error('invalid hasRole args. role must be a String or an Array of Strings');
249+
}
250+
} else {
251+
throw Error("invalid hasRole args. must contain only a 'role argument");
252+
}
253+
fieldConfig.resolve = hasPermission(resources)(resolve);
254+
}
255+
return fieldConfig;
256+
}
257+
});
258+
};
259+
260+
export const roleDirectiveTransformer = (schema: GraphQLSchema, directiveName: string = 'hasRole') => {
261+
return mapSchema(schema, {
262+
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
263+
const roleDirective = getDirective(schema, fieldConfig, directiveName)?.[0];
264+
if (roleDirective) {
265+
const { resolve = defaultFieldResolver } = fieldConfig;
266+
const keys = Object.keys(roleDirective);
267+
let role;
268+
if (keys.length === 1 && keys[0] === 'role') {
269+
role = roleDirective[keys[0]];
270+
if (typeof role === 'string') role = [role];
271+
if (Array.isArray(role)) {
272+
role = role.map((val: any) => String(val));
273+
} else {
274+
throw new Error('invalid hasRole args. role must be a String or an Array of Strings');
275+
}
276+
} else {
277+
throw Error("invalid hasRole args. must contain only a 'role argument");
278+
}
279+
fieldConfig.resolve = hasRole(role)(resolve);
280+
}
281+
return fieldConfig;
282+
}
283+
});
284+
};
285+
286+
export const applyDirectiveTransformers = (schema: GraphQLSchema) => {
287+
return authDirectiveTransformer(roleDirectiveTransformer(permissionDirectiveTransformer(schema)));
288+
};
289+
```
290+
291+
With your transformers defined, apply them on the schema and continue configuring your server instance:
292+
```typescript
293+
...
294+
let schema = makeExecutableSchema({
295+
typeDefs,
296+
resolvers
297+
});
298+
299+
schema = applyDirectiveTransformers(schema);
300+
301+
// Now just passing the schema in the options, configurting the context with Keycloak as before.
302+
const server = new ApolloServer({
303+
schema,
304+
context: ({ req }) => {
305+
return {
306+
kauth: new KeycloakContext({ req }, keycloak)
307+
};
308+
}
309+
});
310+
...
311+
```
312+
203313
### Error Codes
204314

205315
Library will return specific GraphQL errors to the client that can

0 commit comments

Comments
 (0)