mirror of
https://github.com/wassname/talk.git
synced 2026-09-10 12:43:11 +08:00
refactored resolvers, cleaned
This commit is contained in:
+168
-32
@@ -2,59 +2,171 @@ const {
|
||||
ADD_COMMENT_TAG,
|
||||
SEARCH_OTHER_USERS,
|
||||
} = require('../../perms/constants');
|
||||
const property = require('lodash/property');
|
||||
const { property, isBoolean } = require('lodash');
|
||||
|
||||
/**
|
||||
* Decorates the typeResolver with the tags field.
|
||||
* getResolver will get the resolver from the typeResolver or apply the default
|
||||
* resolver.
|
||||
*
|
||||
* @param {Object} typeResolver the type resolver
|
||||
* @param {String} field the field name of the resolver we're getting
|
||||
*/
|
||||
const decorateWithTags = typeResolver => {
|
||||
typeResolver.tags = ({ tags = [] }, _, { user }) => {
|
||||
if (user && user.can(ADD_COMMENT_TAG)) {
|
||||
return tags;
|
||||
const getResolver = (typeResolver, field) => {
|
||||
if (field in typeResolver) {
|
||||
return typeResolver[field];
|
||||
}
|
||||
|
||||
return property(field);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} typeResolver the type resolver
|
||||
* @param {String} field the name of the field being wrapped
|
||||
* @param {Function} customCheck the function that can return a boolean
|
||||
* indicating the resolution status
|
||||
* @param {Function} skipFieldResolver the optional skip resolver that can be used
|
||||
* skip out from the oldFieldResolver.
|
||||
*/
|
||||
const wrapCheck = (
|
||||
typeResolver,
|
||||
field,
|
||||
customCheck,
|
||||
skipFieldResolver = getResolver(typeResolver, field)
|
||||
) => {
|
||||
// Cache the old field resolver. In the event that the check does not return
|
||||
// with a boolean, we'll use this.
|
||||
const oldFieldResolver = getResolver(typeResolver, field);
|
||||
|
||||
// Override the field resolver on the type resolver with this wrapped
|
||||
// function.
|
||||
typeResolver[field] = (obj, args, ctx, info) => {
|
||||
const decision = customCheck(obj, args, ctx, info);
|
||||
if (isBoolean(decision)) {
|
||||
if (decision) {
|
||||
// The custom check returns a boolean true, so we should execute the
|
||||
// underlying field resolver (which may just be the old resolver).
|
||||
return skipFieldResolver(obj, args, ctx, info);
|
||||
}
|
||||
|
||||
// The custom check returns a boolean false, then we should return null,
|
||||
// because the check explicity said that we weren't allowed to access that
|
||||
// field.
|
||||
return null;
|
||||
}
|
||||
|
||||
return tags.filter(t => t.tag.permissions.public);
|
||||
// The custom check yielded no decision, so we should just fall back to the
|
||||
// oldFieldResolver.
|
||||
return oldFieldResolver(obj, args, ctx, info);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* checkPermissions checks that the current user has all the required
|
||||
* permissions.
|
||||
*
|
||||
* @param {Object} ctx graph context
|
||||
* @param {Array<String>} permissions permissions that the user must have
|
||||
*/
|
||||
const checkPermissions = (ctx, permissions) =>
|
||||
!ctx.user || !ctx.user.can(...permissions);
|
||||
|
||||
/**
|
||||
* wrapCheckPermissions will wrap a specific field with a permission check.
|
||||
*
|
||||
* @param {Object} typeResolver the type resolver
|
||||
* @param {String} field the field name of the resolver we're wrapping
|
||||
* @param {Array<String>} permissions array of permissions to check against
|
||||
* @param {Function} fieldResolver base resolver for the field
|
||||
*/
|
||||
const wrapCheckPermissions = (
|
||||
typeResolver,
|
||||
field,
|
||||
permissions,
|
||||
skipFieldResolver = getResolver(typeResolver, field)
|
||||
) =>
|
||||
wrapCheck(
|
||||
typeResolver,
|
||||
field,
|
||||
(obj, args, ctx) => {
|
||||
if (checkPermissions(ctx, permissions)) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
skipFieldResolver
|
||||
);
|
||||
|
||||
/**
|
||||
* decorateWithPermissionCheck will decorate the field resolver with
|
||||
* permission checks.
|
||||
*
|
||||
* @param {Object} typeResolver the type resolver
|
||||
* @param {Object} protect the object with field -> Array<String> of permissions
|
||||
* @param {Function} customCheck a function that can return a boolean based on a
|
||||
* custom check
|
||||
*/
|
||||
const decorateWithPermissionCheck = (typeResolver, protect) => {
|
||||
const decorateWithPermissionCheck = (
|
||||
typeResolver,
|
||||
protect,
|
||||
customCheck = null
|
||||
) => {
|
||||
for (const [field, permissions] of Object.entries(protect)) {
|
||||
let fieldResolver = property(field);
|
||||
if (field in typeResolver) {
|
||||
fieldResolver = typeResolver[field];
|
||||
const baseFieldResolver = getResolver(typeResolver, field);
|
||||
wrapCheckPermissions(typeResolver, field, permissions, baseFieldResolver);
|
||||
|
||||
if (customCheck !== null) {
|
||||
wrapCheck(typeResolver, field, customCheck, baseFieldResolver);
|
||||
}
|
||||
|
||||
typeResolver[field] = (obj, args, ctx, info) => {
|
||||
if (!ctx.user || !ctx.user.can(...permissions)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fieldResolver(obj, args, ctx, info);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* decorateUserField will decorate the user field accesses with correct
|
||||
* permission checks.
|
||||
* checkSelf will check if the current object is the same as the current user.
|
||||
*
|
||||
* @param {String} referenceField the field for the user id to check.
|
||||
*/
|
||||
const checkSelfField = referenceField => (obj, args, ctx) => {
|
||||
if (
|
||||
ctx.user &&
|
||||
obj[referenceField] !== null &&
|
||||
ctx.user.id === obj[referenceField]
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* wrapCheckSelf wraps a typeResolver with a check for self (if the type is
|
||||
* referencing the current user).
|
||||
*
|
||||
* @param {Object} typeResolver the type resolver
|
||||
* @param {String} field the field to decorate
|
||||
* @param {String} referenceField the field to pull the user id from.
|
||||
* @param {Function} fieldResolver base resolver for the field
|
||||
*/
|
||||
const decorateUserField = (typeResolver, field) => {
|
||||
const wrapCheckSelf = (
|
||||
typeResolver,
|
||||
field,
|
||||
referenceField = field,
|
||||
fieldResolver = getResolver(typeResolver, field)
|
||||
) =>
|
||||
wrapCheck(typeResolver, field, checkSelfField(referenceField), fieldResolver);
|
||||
|
||||
/**
|
||||
* decorateUserField will decorate the user field accesses with correct
|
||||
* permission checks and will load the user.
|
||||
*
|
||||
* @param {Object} typeResolver the type resolver
|
||||
* @param {String} field the field to decorate
|
||||
* @param {String} referenceField the field to pull the user id from.
|
||||
*/
|
||||
const decorateUserField = (typeResolver, field, referenceField = field) => {
|
||||
// The default resolver for the user decorator is loading the user by id.
|
||||
let fieldResolver = (obj, args, ctx) => {
|
||||
if (!obj[field]) {
|
||||
if (!obj[referenceField]) {
|
||||
return null;
|
||||
}
|
||||
return ctx.loaders.Users.getByID.load(obj[field]);
|
||||
return ctx.loaders.Users.getByID.load(obj[referenceField]);
|
||||
};
|
||||
|
||||
// The resolver can be overridden however. This decorator will simply wrap the
|
||||
@@ -63,16 +175,39 @@ const decorateUserField = (typeResolver, field) => {
|
||||
fieldResolver = typeResolver[field];
|
||||
}
|
||||
|
||||
typeResolver[field] = (obj, args, ctx, info) => {
|
||||
if (
|
||||
!ctx.user ||
|
||||
obj[field] === null ||
|
||||
(ctx.user.id !== obj[field] && !ctx.user.can(SEARCH_OTHER_USERS))
|
||||
) {
|
||||
return null;
|
||||
// Wrap the current fieldResolver with the resolver which will return the
|
||||
// user.
|
||||
wrapCheckPermissions(
|
||||
typeResolver,
|
||||
field,
|
||||
[SEARCH_OTHER_USERS],
|
||||
fieldResolver
|
||||
);
|
||||
|
||||
// Wrap the checked resolver with a check to see if the current user is the
|
||||
// one being retrieved, in which case we should allow it.
|
||||
wrapCheckSelf(typeResolver, field, referenceField, fieldResolver);
|
||||
};
|
||||
|
||||
/**
|
||||
* decorateWithTags decorates a typeResolver with a tags getter that will
|
||||
* sanitize the tag output for users without permission to see non-public
|
||||
* tags.
|
||||
*
|
||||
* @param {Object} typeResolver base type resolver
|
||||
* @param {Function} fieldResolver the field resolver that gets the tags
|
||||
*/
|
||||
const decorateWithTags = (
|
||||
typeResolver,
|
||||
fieldResolver = getResolver(typeResolver, 'tags')
|
||||
) => {
|
||||
typeResolver.tags = async (obj, args, ctx, info) => {
|
||||
const tags = await fieldResolver(obj, args, ctx, info);
|
||||
if (checkPermissions(ctx, [ADD_COMMENT_TAG])) {
|
||||
return tags;
|
||||
}
|
||||
|
||||
return fieldResolver(obj, args, ctx, info);
|
||||
return tags.filter(t => t.tag.permissions.public);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -80,4 +215,5 @@ module.exports = {
|
||||
decorateUserField,
|
||||
decorateWithTags,
|
||||
decorateWithPermissionCheck,
|
||||
checkSelfField,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user