/** * Permission helpers. * @module app/services/permission/helpers */ import { PERMISSIONS } from "app/constants"; /** * When assigning a permission (any type), store the ID of the user granting the permission */ export function setGrantedByUser(request, response, next) { request.body.granted_by_user_id = request.user.user_id; next(); } /** * Return a middleware function that checks permissions for a specific API method * @param {object} permissions Service API permission dictionary. See Service API documentation. * @param {string} action which action to check, read/create/update/destroy * @return {function} middleware function that checks permissions */ export function checkPermission(permissions, action) { const routePermission = permissions ? action ? permissions[action] : permissions : null; return async function checkPermissionsMiddleware(request, response, next) { const { user } = request; if (!routePermission) { // API access must be explicitly enabled. request.permission = PERMISSIONS.DENY; next(new Error("PermissionsError")); } else if (routePermission.roles?.includes(user.role)) { request.permission = PERMISSIONS.ALLOW; next(); } else if (routePermission.owner) { request.permission = PERMISSIONS.ALLOW_FOR_OWNER; next(); } else if (routePermission.check) { request.permission = await routePermission.check({ request, user, id: request.params?.id, }); if (request.permission === PERMISSIONS.DENY) { next(new Error("PermissionsError")); } else { next(); } } else { request.permission = PERMISSIONS.DENY; next(new Error("PermissionsError")); } }; } /** * Return a middleware function that recursively checks permissions on the parent service of an API method. * Also checks if the parent object exists to begin with. * @param {Service} parentService the service to check. * @return {function} the Express middleware */ export function checkParentPermission(parentService) { return async function checkParentMiddleware(request, response, next) { let permission; request.parents = {}; try { permission = await checkServicePermission(request, parentService); } catch (error) { console.error(error); permission = PERMISSIONS.DENY; } request.permission = permission; if (permission === PERMISSIONS.DENY) { next(new Error("PermissionsError")); } else { next(); } }; } /** * Helper function to check permissions on a service * @param {Request} request the Express request we're checking * @param {Service} service the Service we're checking * @return {permission} the evaluated permissions */ async function checkServicePermission(request, service) { const user = request.user; const readPermissions = service.options.permissions.read; if (!readPermissions) { return PERMISSIONS.DENY; } /** Check read permissions on any parent objects first. */ if (service.parent) { const parentPermission = await checkServicePermission( request, service.parent ); if (parentPermission === PERMISSIONS.DENY) { return PERMISSIONS.DENY; } } /** Make sure this service's object exists before proceeding. */ const idAttribute = service.Model.prototype.idAttribute; const objectId = request.params[idAttribute]; const object = await new service.Model({ [idAttribute]: objectId }).fetch(); request.parents[service.resource] = object; if (!object) { /** If there is no object, deny */ return PERMISSIONS.DENY; } else if (readPermissions.roles?.includes(user.role)) { /** Pass role permission check */ return PERMISSIONS.ALLOW; } else if (readPermissions.owner && object.get("user_id") === user.user_id) { /** Pass owner permission check */ return PERMISSIONS.ALLOW; } else if (readPermissions.check) { /** Pass custom permission check */ return await readPermissions.check({ request, user, id: objectId, }); } else { /** No checks passed - deny */ return PERMISSIONS.DENY; } }