Last active
April 21, 2018 12:12
-
-
Save giautm/fc82ef0a53ec560dd8084d427e9c6350 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { defaultFieldResolver } from 'graphql'; | |
import { SchemaDirectiveVisitor } from 'graphql-tools'; | |
import { createError } from 'apollo-errors'; | |
export const UserPerms = Symbol('Get User Permissions'); | |
export const NotAuthorizedAll = createError('NotAuthorized', { | |
message: 'Not authorized, required ALL of the permissions', | |
}); | |
export const NotAuthorizedOne = createError('NotAuthorized', { | |
message: 'Not authorized, required ONE of the permissions', | |
}); | |
export class AuthDirective extends SchemaDirectiveVisitor { | |
visitFieldDefinition(field, details) { | |
this.ensureFieldsWrapped(details.objectType); | |
field._requiredPerms = this.args.requires; | |
field._operationName = this.args.opName; | |
} | |
visitObject(type) { | |
this.ensureFieldsWrapped(type); | |
type._requiredPerms = this.args.requires; | |
type._operationName = this.args.opName; | |
} | |
ensureFieldsWrapped(objectType) { | |
// Mark the GraphQLObjectType object to avoid re-wrapping: | |
if (objectType._authFieldsWrapped) return; | |
objectType._authFieldsWrapped = true; | |
const fields = objectType.getFields(); | |
Object.keys(fields).forEach(fieldName => { | |
const field = fields[fieldName]; | |
const { resolve = defaultFieldResolver } = field; | |
field.resolve = async function (...args) { | |
// Get the required Permissions from the field first, falling back | |
// to the objectType if no Permissions is required by the field: | |
const { | |
_requiredPerms: requiredPerms, | |
_operationName: opName = 'ALL', | |
} = (field._requiredPerms) ? field : objectType; | |
if (!requiredPerms) { | |
return resolve.apply(this, args); | |
} | |
const context = args[2]; | |
const getPermsAsync = context[UserPerms]; | |
if (typeof getPermsAsync === 'function') { | |
const perms = await getPermsAsync(); | |
this[`_check${opName}`](perms, requiredPerms); | |
} | |
return resolve.apply(this, args); | |
}; | |
}); | |
} | |
_checkALL(perms, requiredPerms) { | |
if (Array.isArray(perms) && requiredPerms.every(p => perms.includes(p))) { | |
return; | |
} | |
throw new NotAuthorizedAll({ requiredPerms }); | |
} | |
_checkONE(perms, requiredPerms) { | |
if (Array.isArray(perms) && requiredPerms.some(p => perms.includes(p))) { | |
return; | |
} | |
throw new NotAuthorizedOne({ requiredPerms }); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
directive @auth( | |
requires: Role = ADMIN, | |
opName: Operator = ALL, | |
) on OBJECT | FIELD_DEFINITION | |
enum Operator { | |
ALL | |
ONE | |
} | |
enum Role { | |
ADMIN | |
REVIEWER | |
USER | |
UNKNOWN | |
} | |
type User @auth(requires: [USER]) { | |
name: String | |
banned: Boolean @auth(requires: [ADMIN]) | |
canPost: Boolean @auth(requires: [REVIEWER,USER]) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment