Skip to content

Instantly share code, notes, and snippets.

@giautm
Last active April 21, 2018 12:12
Show Gist options
  • Save giautm/fc82ef0a53ec560dd8084d427e9c6350 to your computer and use it in GitHub Desktop.
Save giautm/fc82ef0a53ec560dd8084d427e9c6350 to your computer and use it in GitHub Desktop.
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 });
}
}
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