Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save FluorescentHallucinogen/75d12beb731f1a253c8f188919d5f5a7 to your computer and use it in GitHub Desktop.
Save FluorescentHallucinogen/75d12beb731f1a253c8f188919d5f5a7 to your computer and use it in GitHub Desktop.
@hasRole GraphQL custom directive
directive @hasRole (
OR: [ConstraintInput!]
NOT: [ConstraintInput!]
AND: [ConstraintInput!]
has_some: [UserRole!]
has_every: [UserRole!]
) on OBJECT | FIELD_DEFINITION
input ConstraintInput {
OR: [ConstraintInput!]
NOT: [ConstraintInput!]
AND: [ConstraintInput!]
has_some: [UserRole!]
has_every: [UserRole!]
}
enum UserRole {
ADMIN
EDITOR
MANAGER
}
const { defaultFieldResolver } = require('graphql');
const { SchemaDirectiveVisitor } = require('graphql-tools');
const { createError } = require('apollo-errors');
const { validate } = require('./validation');
class HasRole extends SchemaDirectiveVisitor {
visitObject(type) {
this.ensureFieldsWrapped(type);
type._authRule = this.args;
}
visitFieldDefinition(field, details) {
this.ensureFieldsWrapped(details.objectType);
field._authRule = this.args;
}
ensureFieldsWrapped(objectType) {
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) {
const rule =
field._authRule ||
objectType._authRule;
if (!rule) {
return resolve.apply(this, args);
}
//const roles = ['ADMIN', 'EDITOR'];
//const { roles } = await getUser(args[2]);
const errors = validate(roles, rule);
if (errors && errors.length) {
const AuthorizationError = createError('AuthorizationError', {
message: 'Access denied',
internalData: {
rule,
errors
}
});
throw new AuthorizationError();
}
return resolve.apply(this, args);
}
});
}
}
module.exports = HasRole;
class ValidationError extends Error {
constructor(message) {
super(message || 'Validation error');
this.name = 'ValidationError';
}
}
class NegationError extends ValidationError {
constructor(condition) {
super('Invalid negation in logical expression');
this.name = 'NegationError';
this.condition = condition;
}
}
const validators = {
has_some: (value, list) => {
const values = [].concat(value);
if (!list.some(item => values.includes(item))) {
throw new ValidationError();
}
},
has_every: (value, list) => {
const values = [].concat(value);
if (!list.every(item => values.includes(item))) {
throw new ValidationError();
}
}
};
function validate(value, condition) {
/*
if (Array.isArray(condition)) {
return handleOrCondition(condition)
} else
*/
if (condition && typeof condition === 'object') {
return handleObjectCondition(condition);
} else {
throw new Error('Invalid validation rule');
}
function handleObjectCondition(props) {
return [].concat(...Object.entries(props).map(([key, param]) => {
switch (key) {
case 'OR': return handleOrCondition(param);
case 'AND': return handleAndCondition(param);
case 'NOT': return handleNotCondition(param);
default: return handleValidation(key, param);
}
}));
}
function handleAndCondition(list) {
return [].concat(...list.map(handleObjectCondition));
}
function handleOrCondition(list) {
const hasError = res => res.length;
const fails = list.map(handleObjectCondition).filter(hasError);
return fails.length === list.length ? [].concat(...fails) : [];
}
function handleNotCondition(list) {
const hasError = res => res.length;
const fails = list.map(handleObjectCondition).filter(hasError);
//return fails.length !== list.length ? [new NegationError(list)] : [];
return fails.length ? [] : [new NegationError(list)];
}
function handleValidation(key, param) {
const validate = validators[key];
if (!validate) {
throw new Error(`Unknown validator: ${key}`);
}
try {
validate(value, param);
} catch (error) {
if (error instanceof ValidationError) {
error.condition = { [key]: param };
return [error];
} else {
throw error;
}
}
return [];
}
}
module.exports = {
validate
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment