diff --git a/client/components/Coordinator/Evaluation/EvaluationList.js b/client/components/Coordinator/Evaluation/EvaluationList.js index c5668b18..cdd5f971 100644 --- a/client/components/Coordinator/Evaluation/EvaluationList.js +++ b/client/components/Coordinator/Evaluation/EvaluationList.js @@ -52,15 +52,13 @@ const EvaluationList = () => { useEffect(() => { // 1. Find all CourseEvaluations where the createdBy key matches the logged in user services['course-evaluation'].find(); - services['users'].find(); setLoading(false); }, []); const courseEvaluations = useSelector((state) => state['course-evaluation']) ?.queryResult?.data; - const users = useSelector((state) => state['users'])?.queryResult?.data; - if (loading || !users || !courseEvaluations) { + if (loading || !courseEvaluations) { return ( Loading... @@ -76,15 +74,9 @@ const EvaluationList = () => { // 3. Render course list elemnts evaluationListings = evaluationListings.map( - ({ _id, courseId, reviewDescription }) => { + ({ _id, courseId, reviewDescription, coordinators }) => { // select out the coordinators with the permission for this evaluation - const coordinators = users.filter(({ perms }) => - perms.some( - ({ course_id, role }) => course_id === _id && role === 'Coordinator' - ) - ); - return ( { const classes = useStyles(); - const coordinatorNames = coordinators.map(({ name }) => name)?.join(', '); + const coordinatorNames = coordinators.map(({ name,email }) => name || email)?.join(', '); return (

{courseCode}

-

{coordinatorNames}.

+

{coordinatorNames}

{evaluationDescription}
diff --git a/client/components/Coordinator/EvaluationOverview/General/Controls/ManageReviewers.js b/client/components/Coordinator/EvaluationOverview/General/Controls/ManageReviewers.js index b4d739d3..ad33bf72 100644 --- a/client/components/Coordinator/EvaluationOverview/General/Controls/ManageReviewers.js +++ b/client/components/Coordinator/EvaluationOverview/General/Controls/ManageReviewers.js @@ -29,49 +29,24 @@ const useStyles = makeStyles({ import { useSelector } from 'react-redux'; import { services, rawServices } from 'store/feathersClient'; -import { useState, useEffect } from 'react'; +import { useState } from 'react'; const ManageReviewers = ({ evaluationID }) => { const classes = useStyles(); const [modal, setModal] = useState(false); const [email, setEmail] = useState(''); - useEffect(() => { - services['users'].find({ - perms: { - $in: [{ course_id: evaluationID, role: 'Reviewer' }], - }, - }); - }, []); - const courseEval = useSelector((state) => state['course-evaluation']); const evalData = courseEval?.data; - const users = useSelector((state) => state['users']); - const userData = users?.queryResult?.data; // selects out all reviewers with correct permission - const reviewers = userData.filter((user) => - user.perms.reduce( - (acc, permission) => - acc || - (permission.course_id == evaluationID && permission.role == 'Reviewer'), - false - ) - ); + const reviewers = evalData?.reviewers || []; // removes a user from the evaluation const removePermission = async (userId, evaluationId) => { try { - const oldPermissions = reviewers.find((user) => user._id == userId).perms; - const newPerms = oldPermissions.filter( - (permission) => - !( - permission.course_id == evaluationId && - permission.role == 'Reviewer' - ) - ); - const response = await services['users'].patch(userId, { - perms: newPerms, + services['users'].patch(userId, { + $pull: { perms: {course_id:evaluationId,role:'Reviewer'}} }); } catch (error) { console.error(error); diff --git a/client/components/Coordinator/EvaluationOverview/General/Controls/ReviewerListing.js b/client/components/Coordinator/EvaluationOverview/General/Controls/ReviewerListing.js index 457e61d6..9701ddba 100644 --- a/client/components/Coordinator/EvaluationOverview/General/Controls/ReviewerListing.js +++ b/client/components/Coordinator/EvaluationOverview/General/Controls/ReviewerListing.js @@ -24,7 +24,6 @@ const useStyles = makeStyles({ const ReviewerListing = ({ email, name, - inviter, googleId, removeReviewer, }) => { diff --git a/client/components/Coordinator/EvaluationOverview/General/OtherInformation.js b/client/components/Coordinator/EvaluationOverview/General/OtherInformation.js index 673e8be1..4b6f137c 100644 --- a/client/components/Coordinator/EvaluationOverview/General/OtherInformation.js +++ b/client/components/Coordinator/EvaluationOverview/General/OtherInformation.js @@ -2,27 +2,20 @@ import Card from 'components/MaterialKit/Card/Card.js'; import CardBody from 'components/MaterialKit/Card/CardBody.js'; import CardHeader from 'components/MaterialKit/Card/CardHeader.js'; +import Grid from 'components/MaterialKit/Grid/GridContainer.js'; +import GridItem from 'components/MaterialKit/Grid/GridItem.js'; // Store Actions and Redux import { useSelector } from 'react-redux'; -import { services } from 'store/feathersClient'; -import { useEffect } from 'react'; const OtherInformation = () => { - useEffect(() => { - services['users'].find(); - }, []); - const courseEval = useSelector((state) => state['course-evaluation']); const evalData = courseEval?.data; - const users = useSelector((state) => state['users']); - const userData = users?.queryResult?.data; + const coordinators = evalData?.coordinators || []; const createdOn = new Date(evalData?.createdAt); - const createdBy = evalData?.createdBy; - const author = userData?.find((elem) => elem._id == createdBy); const dateString = createdOn?.toLocaleDateString('en-gb', { year: 'numeric', month: 'short', @@ -33,11 +26,18 @@ const OtherInformation = () => { Other Information -

Created by:

-

- {author?.name ?? 'Unknown creator'} (on {dateString ?? 'unknown date'} - ) -

+ + +

Coordinators

+ {coordinators.map(({name},index) =>

{name}

)} +
+ +

Date Started

+

+ {dateString ?? 'unknown date'} +

+
+
); diff --git a/client/components/Coordinator/EvaluationOverview/Reviews/ReviewProgress/ProgressDisplay.js b/client/components/Coordinator/EvaluationOverview/Reviews/ReviewProgress/ProgressDisplay.js index 381708fe..0cf7c18f 100644 --- a/client/components/Coordinator/EvaluationOverview/Reviews/ReviewProgress/ProgressDisplay.js +++ b/client/components/Coordinator/EvaluationOverview/Reviews/ReviewProgress/ProgressDisplay.js @@ -55,9 +55,10 @@ const ProgressDisplay = ({ reviewer, review }) => { display="inline-block" color="white" onClick={handleView} + disabled={!review?._id} > - View + View {review?.submittedDate && ( diff --git a/client/components/Coordinator/EvaluationOverview/Reviews/ReviewProgress/index.js b/client/components/Coordinator/EvaluationOverview/Reviews/ReviewProgress/index.js index f1a98a5f..d1d3decd 100644 --- a/client/components/Coordinator/EvaluationOverview/Reviews/ReviewProgress/index.js +++ b/client/components/Coordinator/EvaluationOverview/Reviews/ReviewProgress/index.js @@ -12,62 +12,40 @@ import { useSelector } from 'react-redux'; import { services } from 'store/feathersClient'; import { useRouter } from 'next/router'; -import { useState, useEffect } from 'react'; +import { useEffect } from 'react'; const ReviewProgress = () => { const router = useRouter(); - const [reviewers, setReviewers] = useState([]); - const [loading, setLoading] = useState(true); - - const hasPath = router.query.hasOwnProperty('courseID'); - + const { courseID } = router.query; useEffect(() => { - if (router.query.hasOwnProperty('courseID')) { - const { courseID } = router.query; - - services['review'].find({ - course_id: courseID, - }); - services['users'].find( - {query: - { - perms: { - $elemMatch: { course_id: courseID, role: 'Reviewer' }, - } - } - } - ); - } - }, [hasPath]); - - if (hasPath) { - const reviews = useSelector((state) => state['review']); - const reviewData = reviews?.queryResult?.data; - - const users = useSelector((state) => state['users']); - - const usersData = users?.queryResult?.data; - - // TODO Note: Something similar is done in utils/compileResult - const progressCards = usersData?.map(reviewer=>{ - const reviewOfUser = reviewData?.find(review=> review.user_id === reviewer._id) || {}; - return( - - ); + services['review'].find({ + course_id: courseID, }); - - return ( - - Review Progress - - {progressCards} - - + }, []); + + const courseEval = useSelector((state) => state['course-evaluation']); + const evalData = courseEval?.data; + const usersData = evalData?.reviewers; + const reviews = useSelector((state) => state['review']); + const reviewData = reviews?.queryResult?.data; + + // TODO Note: Something similar is done in utils/compileResult + const progressCards = usersData?.map(reviewer=>{ + const reviewOfUser = reviewData?.find(review=> review.user_id === reviewer._id) || {}; + return( + ); - } else { - return

invalid...

; - } + }); + + return ( + + Review Progress + + {progressCards} + + + ); }; export default ReviewProgress; diff --git a/client/components/reviewer/ReviewList.js b/client/components/reviewer/ReviewList.js index a5beab2b..f0e1dcb3 100644 --- a/client/components/reviewer/ReviewList.js +++ b/client/components/reviewer/ReviewList.js @@ -45,8 +45,7 @@ const EvaluationList = () => { const authUser = useSelector((state) => state.auth.user); useEffect(() => { - // 1. Find all CourseEvaluations where the createdBy key matches the logged in user - services['course-evaluation'].find(); + services['course-evaluation'].find(); // This is already a filtered lists based on perms }, []); // Executes on Component Remount (after auth user is fetched) diff --git a/client/components/reviewer/ReviewListing.js b/client/components/reviewer/ReviewListing.js index 9a2296da..b46194be 100644 --- a/client/components/reviewer/ReviewListing.js +++ b/client/components/reviewer/ReviewListing.js @@ -23,14 +23,14 @@ const ReviewListing = ({ }) => { const classes = useStyles(); - const coordinatorNames = coordinators?.join(', '); + const coordinatorNames = coordinators?.map(({name,email})=>name || email).join(', '); return (

{courseCode}

-

{coordinatorNames}.............

+

{coordinatorNames}

{evaluationDescription}
diff --git a/client/pages/coordinator/[courseID]/review/compiled.js b/client/pages/coordinator/[courseID]/review/compiled.js index 1113ca8b..3358bc4b 100644 --- a/client/pages/coordinator/[courseID]/review/compiled.js +++ b/client/pages/coordinator/[courseID]/review/compiled.js @@ -24,19 +24,11 @@ const CompiledPage = () => { course_id: courseID }, }); //Accessed by queryResult.data - services['users'].find( - {query: - { - perms: { - $elemMatch: { course_id: courseID, role: 'Reviewer' }, - } - } - } - ); // Accessed by queryResult.data }, [courseID]); - const userState = useSelector(state=> state.users); - const reviewers = userState?.queryResult.data; + const courseEval = useSelector((state) => state['course-evaluation']); + const evalData = courseEval?.data; + const reviewers = evalData?.reviewers || []; const reviewState = useSelector(state=> state.review); const reviews = reviewState?.queryResult.data; diff --git a/mkdocs/docs/developer/backend/permission.md b/mkdocs/docs/developer/backend/permission.md new file mode 100644 index 00000000..3f908c4d --- /dev/null +++ b/mkdocs/docs/developer/backend/permission.md @@ -0,0 +1,56 @@ +# Permission +Permission refers to the authority of the user to access or modify information. In business rules, the only reason why a user has to have permission if they need to have access to that information or authority to have action. This means that if a user does not need to access that information, they should not have it. + +In IndEAA, this is essential in restricting each of the users to perform only actions that they have permission over. The permission has 2 main elements: + +- `role` (what sets of permission does the user have?) +- `course_id` (which `course_evaluation` where this role persists?) + + +## Relationship to Frontend +Although this part of the documentation applies for both the backend and frontend, this documentation is mainly for backend as it is where most business rules will apply. Permissions for frontend mainly apply only for the convenience of the user, but does not protect certain information or action from being exposed. Hence, all data-driven action (CRUD operations) should be protected by the backend. + +## Role + +### Administrator +Administrators have the capability to adjust all the permissions of the user in the system, and even has the ability to delete users from the system. + +### Coordinator +Coordinators have the capability adjust everything related to the `course_evaluation` and should be able to view details about other coordinators and reviewers of their `course_evaluation` + +### Reviewer + +Reviewers have the capability to adjust everything related to their own `review` for a `course_evaluation`, and should be able to view details about coordinators for the `course_evaluation` they are assigned to. + +## Hooks + +### Role-Based Restriction +This hook restricts actions/service methods only to users with a specific role regardless of `course_id`. + +???+ example "Examples of Times where you want this" + + - Admiinistrator Role + + The administrator role does not need `course_id`. Hence, should be used for it + + - Coordinator Role + + Creating a `course_evaluation` requires a Coordinator role, but does not need to check for `course_id` + +### Filter-Based Restriction +This hook restricts query (`GET` and `FIND` service methods) to limit (`FIND`) based on query or restricts access (`GET`). + +### Role-And-Course Based Restriction +This hook is a stricter version of `Role-Based Restrictions` as this also applies with `course_id` that the user has access to. + +???+ example "Example of Times where you want this" + + - Multi-Role Coordinator + + A coordinator should not have access to other `course_evaluation` they do not have access on. + + - Multi-Role Reviewer + + A reviewer should not have access to `course_evaluation` they do not have access on, and should also not be able to view `review` that they are not the review person for. + + \ No newline at end of file diff --git a/mkdocs/docs/developer/backend/services.md b/mkdocs/docs/developer/backend/services.md new file mode 100644 index 00000000..b1f5bbed --- /dev/null +++ b/mkdocs/docs/developer/backend/services.md @@ -0,0 +1,30 @@ +# Services +Services refers to endpoints of the backend that interfaces directly with the database and may have relations to other service (as opposed to microservices). + +See below for the services that IndEAA has. + +## Users `/users` +This is responsible for data-driven actions related to users. + +## Course Evaluation`/course_evaluation` +This is responsible for data-driven actions related to course that is being evaluated. + +### Metadata +This service has two metadata field that are not mapped in the model. + +### `coordinators` + +This is an array of users object that has a permission `course_id` for this `course_evaluation` and `role` as "Coordinator. The object has these following fields: + +- `_id` +- `name` +- `email` + +### `reviewers` +This is an array of users object that has a permission `course_id` for this `course_evaluation` and `role` as "Reviewer". The object has the same following fields as `coordinators` object structure. + +???+ note "Model != Service" + There is some difference to a service to a model field, hence should not be relied upon if deciding on which endpoint to use. + +## Review `/review` +This is responsible for data-driven actions related to reviews for a specific course. \ No newline at end of file diff --git a/mkdocs/docs/developer/feathers_redux.md b/mkdocs/docs/developer/frontend/feathers_redux.md similarity index 100% rename from mkdocs/docs/developer/feathers_redux.md rename to mkdocs/docs/developer/frontend/feathers_redux.md diff --git a/mkdocs/docs/developer/notifications.md b/mkdocs/docs/developer/frontend/notifications.md similarity index 100% rename from mkdocs/docs/developer/notifications.md rename to mkdocs/docs/developer/frontend/notifications.md diff --git a/mkdocs/mkdocs.yml b/mkdocs/mkdocs.yml index 5019be35..5ef2ab16 100644 --- a/mkdocs/mkdocs.yml +++ b/mkdocs/mkdocs.yml @@ -76,9 +76,12 @@ nav: - Introduction: developer/index.md - Coding Standards: developer/coding_standards.md - Data Engineering: developer/data_engineering.md - - Transport/Integration Layer (Feathers-redux): developer/feathers_redux.md - Database Support Scripts: developer/database_support_scripts.md - - Notifications with Redux Saga: developer/notifications.md - Frontend: - Component vs Page: developer/frontend/component_vs_page.md + - Transport/Integration Layer (Feathers-redux): developer/frontend/feathers_redux.md + - Notifications with Redux Saga: developer/frontend/notifications.md + - Backend: + - Permission: developer/backend/permission.md + - Services: developer/backend/services.md - Changelog: changelog.md diff --git a/server/.eslintrc.json b/server/.eslintrc.json index fa5d75ff..8b813e86 100644 --- a/server/.eslintrc.json +++ b/server/.eslintrc.json @@ -33,6 +33,7 @@ "unused-imports/no-unused-vars": [ "warn", { "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" } - ] + ], + "no-console": ["error", { "allow": ["warn","info","error","groupCollapsed","groupEnd"] }] } } \ No newline at end of file diff --git a/server/src/hooks/add-coordinators-and-reviewers-to-evaluation.js b/server/src/hooks/add-coordinators-and-reviewers-to-evaluation.js new file mode 100644 index 00000000..68121bf4 --- /dev/null +++ b/server/src/hooks/add-coordinators-and-reviewers-to-evaluation.js @@ -0,0 +1,44 @@ +// Use this hook to manipulate incoming or outgoing data. +// For more information on hooks see: http://docs.feathersjs.com/api/hooks.html + +// This hook is used for adding more information about the course-evaluation endpoint +// This is an After Hook +const errors = require('@feathersjs/errors'); + +// eslint-disable-next-line no-unused-vars +module.exports = function (options = {}) { + return async context => { + const {app, id,method} = context; + + // Function for selection only a couple of information from each user + // that has a role in a course + const getUsersForRole = async (course_id,role) =>{ + const query = await app.service('users').find({query: + { + $select:['_id','name','email'], + perms: { + $elemMatch: { course_id: course_id, role}, + } + } + }); + return query.data; + + }; + + if(method!=='find'){ // All Services except find + const [coordinators,reviewers] = await Promise.all([getUsersForRole(id,'Coordinator'),getUsersForRole(id,'Reviewer')]); + context.result.coordinators = coordinators; + context.result.reviewers = reviewers; + } + else{ + context.result.data = await Promise.all(context.result.data.map(async(datum)=>{ + const [coordinators,reviewers] = await Promise.all([getUsersForRole(datum._id,'Coordinator'),getUsersForRole(datum._id,'Reviewer')]); + return({ + ...datum, + coordinators, + reviewers + }); + })); + } + }; +}; diff --git a/server/src/hooks/attach-user.js b/server/src/hooks/attach-user.js index db8b4193..bc83fabe 100644 --- a/server/src/hooks/attach-user.js +++ b/server/src/hooks/attach-user.js @@ -11,9 +11,7 @@ module.exports = (options = {}) => { if(context.params.authStrategies && context.params.authStrategies.includes('google')) { const googleId = context.params.query.googleId; - console.log(googleId); context.params.user = (await userModel.findOne({googleId}))._doc; - console.log(context.user); } return context; diff --git a/server/src/hooks/filter-based-permission.js b/server/src/hooks/filter-based-permission.js index 30b6070b..4990603e 100644 --- a/server/src/hooks/filter-based-permission.js +++ b/server/src/hooks/filter-based-permission.js @@ -1,6 +1,7 @@ // Use this hook to manipulate incoming or outgoing data. // For more information on hooks see: http://docs.feathersjs.com/api/hooks.html +const { Forbidden } = require('@feathersjs/errors'); const { getAvailablePermissionsOfUser} = require('../utils'); @@ -15,7 +16,7 @@ const { getAvailablePermissionsOfUser} = require('../utils'); */ module.exports = (options = {}) => { return async context => { - const {serviceName,params} = context; + const {serviceName,params,method, id} = context; // No filter in internal server use if (typeof params.provider!=='undefined'){ @@ -36,9 +37,14 @@ module.exports = (options = {}) => { { // Convert to set to eliminate redundancies const courseIDsAllowed = Array.from(new Set(coordinatorCoursePermissions.concat(reviewerCoursePermissions))); + const courseIDsAllowedStringCasted = courseIDsAllowed.map(course_id => String(course_id)); + if(method==='get' && !courseIDsAllowedStringCasted.includes(id)){ + throw new Forbidden('You do not have the correct permission to access this'); + } params.query = {...params.query, _id: {$in: courseIDsAllowed} }; } else if(serviceName==='review'){ + // TODO: Create a permission filter for a review params.query={ ...params.query, $or: [ @@ -49,7 +55,6 @@ module.exports = (options = {}) => { } } } - console.log(params.query); return context; }; }; diff --git a/server/src/hooks/role-and-course-based-restrictions.js b/server/src/hooks/role-and-course-based-restrictions.js index 36f964ce..210c8389 100644 --- a/server/src/hooks/role-and-course-based-restrictions.js +++ b/server/src/hooks/role-and-course-based-restrictions.js @@ -22,12 +22,17 @@ module.exports = () => { return false; // Allow service } + // Difficult to do comparison when Mongoose Object ID is being compared + const userPermissionsWithCourseIdCastedAsString = params.user.perms.map(({course_id,role})=> { + String(course_id), + role; + }); if(serviceName==='review'){ // This is for review service + // TODO: this needs to be improve to only accomodate authors // Get course id const course_id = context.data.course_id || context.existing.course_id; - // If the service is a review, then the user needs to have a Reviewer role - if (!params.user.perms.includes({course_id,role:'Reviewer'})){ + if (userPermissionsWithCourseIdCastedAsString.includes({course_id,role:'Reviewer'})){ return true; } } @@ -37,7 +42,7 @@ module.exports = () => { const course_id = context.data._id || context.existing._id; // If the service is a course-evaluation role - if (!params.user.perms.includes({course_id,role:'Coordinator'})){ + if (userPermissionsWithCourseIdCastedAsString.includes({course_id,role:'Coordinator'})){ return true; } } diff --git a/server/src/hooks/role-based-restrictions.js b/server/src/hooks/role-based-restrictions.js index c8202e21..41151e83 100644 --- a/server/src/hooks/role-based-restrictions.js +++ b/server/src/hooks/role-based-restrictions.js @@ -25,7 +25,6 @@ module.exports = (roleBasedPermissionsRequired) => { for (let roleRequired of roleBasedPermissionsRequired ){ // If a user does not have the required permission, then disallow if(!availablePerms.has(roleRequired)){ - console.log(roleRequired); return true; } } diff --git a/server/src/services/course-evaluation/course-evaluation.hooks.js b/server/src/services/course-evaluation/course-evaluation.hooks.js index ae88322a..75de519f 100644 --- a/server/src/services/course-evaluation/course-evaluation.hooks.js +++ b/server/src/services/course-evaluation/course-evaluation.hooks.js @@ -1,21 +1,25 @@ const { authenticate } = require('@feathersjs/authentication').hooks; +const {iff, disallow } = require('feathers-hooks-common'); const filterBasedPermission = require('../../hooks/filter-based-permission'); +const roleBasedRestrictions = require('../../hooks/role-based-restrictions'); +const roleAndCourseBasedRestrictions = require('../../hooks/role-and-course-based-restrictions'); +const addCoordinatorsAndReviewersToEvaluation = require('../../hooks/add-coordinators-and-reviewers-to-evaluation'); const addPermissionToUser = require('../../hooks/add-permission-to-user'); module.exports = { before: { all: [ authenticate('jwt') ], find: [filterBasedPermission()], - get: [], - create: [], - update: [], - patch: [], - remove: [] + get: [filterBasedPermission()], + create: [ roleBasedRestrictions(['Coordinator'])], // Only create for a coordinator + update: [ roleAndCourseBasedRestrictions()], // Only modifications of a specific unit + patch: [ roleAndCourseBasedRestrictions()], // is allowed for a the coordinator of that specific unit + remove: [disallow('external')] // Prevent any removal of Course-Evaluation }, after: { - all: [], + all: [addCoordinatorsAndReviewersToEvaluation()], find: [], get: [], create: [addPermissionToUser()], diff --git a/server/src/services/review/review.hooks.js b/server/src/services/review/review.hooks.js index 48dd858c..b242501c 100644 --- a/server/src/services/review/review.hooks.js +++ b/server/src/services/review/review.hooks.js @@ -1,15 +1,18 @@ const { authenticate } = require('@feathersjs/authentication').hooks; +const {iff, disallow } = require('feathers-hooks-common'); const filterBasedPermission = require('../../hooks/filter-based-permission'); +const roleAndCourseBasedRestrictions = require('../../hooks/role-and-course-based-restrictions'); + module.exports = { before: { all: [ authenticate('jwt') ], find: [filterBasedPermission()], - get: [], - create: [], - update: [], - patch: [], - remove: [] + get: [filterBasedPermission()], + create: [roleAndCourseBasedRestrictions()], // Creation and modificaiton of review + update: [roleAndCourseBasedRestrictions()], // is only allowed for the author/reviewer + patch: [roleAndCourseBasedRestrictions()], + remove: [disallow('external')] }, after: { diff --git a/server/src/services/users/users.hooks.js b/server/src/services/users/users.hooks.js index 9f99ed1d..bd0e79d7 100644 --- a/server/src/services/users/users.hooks.js +++ b/server/src/services/users/users.hooks.js @@ -9,14 +9,13 @@ module.exports = { before: { all: [], find: [authenticate('jwt'), - // attachUser(), roleBasedRestrictions(['Coordinator']) ], get: [authenticate('jwt')], create: [firstUser(), compileGoogleAndSystemPerms()], - update: [authenticate('jwt')], - patch: [authenticate('jwt')], - remove: [authenticate('jwt')], + update: [authenticate('jwt'), roleBasedRestrictions(['Administrator'])], // Only Admin + patch: [authenticate('jwt'), roleBasedRestrictions(['Coordinator'])], // Only Coordinator + Admin + remove: [authenticate('jwt'), roleBasedRestrictions(['Administrator'])], // Only Admin }, after: {