Skip to content

Commit 081ba92

Browse files
chore: update routes to use new RBAC rules
1 parent 5b56835 commit 081ba92

File tree

2 files changed

+116
-33
lines changed

2 files changed

+116
-33
lines changed

server/src/routes/v2/surveys.ts

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { subject } from '@casl/ability';
21
import { accessibleBy } from '@casl/mongoose';
32
import express, { NextFunction, Response } from 'express';
43

@@ -40,15 +39,14 @@ router.get(
4039
'/',
4140
[auth],
4241
async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
42+
if (!req.authorization?.can(ACTIONS.CASL.READ, SUBJECTS.SURVEY)) {
43+
return res.sendStatus(403);
44+
}
4345
try {
4446
const result = await Survey.find({
4547
$and: [
4648
req.query,
47-
req?.authorization
48-
? accessibleBy(req.authorization).ofType(
49-
Survey.modelName
50-
)
51-
: {},
49+
accessibleBy(req.authorization).ofType(Survey.modelName),
5250
{ deletedAt: null }
5351
]
5452
});
@@ -94,22 +92,26 @@ router.get(
9492
'/:objectId',
9593
[auth],
9694
async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
97-
if (
98-
!req.authorization?.can(
99-
ACTIONS.CASL.READ,
100-
subject(SUBJECTS.SURVEY, { _id: req.params.objectId })
101-
)
102-
) {
95+
// Check for basic read permission (more permission checks happen within Mongo query)
96+
if (!req.authorization?.can(ACTIONS.CASL.READ, SUBJECTS.SURVEY)) {
10397
return res.sendStatus(403);
10498
}
10599
try {
106100
const result = await Survey.findOne({
107-
_id: req.params.objectId,
108-
deletedAt: null
101+
$and: [
102+
{ _id: req.params.objectId },
103+
accessibleBy(req.authorization).ofType(Survey.modelName), // Need to pass in dynamic filter here because access is determined by `createdAt` and `locationObjectId` inside of Survey
104+
{ deletedAt: null }
105+
]
109106
});
110107
// Survey not found
111108
if (!result) {
112-
return res.status(404).json({ message: 'Survey not found' });
109+
return res
110+
.status(404)
111+
.json({
112+
message:
113+
'Survey not found or you do not have permission to read this survey'
114+
});
113115
}
114116
// Successfully fetched survey
115117
res.status(200).json({
@@ -154,22 +156,33 @@ router.patch(
154156
'/:objectId',
155157
[auth, validate(updateSurveySchema)],
156158
async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
157-
if (
158-
!req.authorization?.can(
159-
ACTIONS.CASL.UPDATE,
160-
subject(SUBJECTS.SURVEY, { _id: req.params.objectId })
161-
)
162-
) {
159+
// Basic check if user is allowed to update any survey at all
160+
if (!req.authorization?.can(ACTIONS.CASL.UPDATE, SUBJECTS.SURVEY)) {
163161
return res.sendStatus(403);
164162
}
165163
try {
164+
// Update access is document-dependent but not field-dependent, so we only need to pass in
165+
// an accessibleBy filter here
166166
const result = await Survey.findOneAndUpdate(
167-
{ _id: req.params.objectId, deletedAt: null },
167+
{
168+
$and: [
169+
{ _id: req.params.objectId },
170+
accessibleBy(req.authorization).ofType(
171+
Survey.modelName
172+
),
173+
{ deletedAt: null }
174+
]
175+
},
168176
req.body,
169177
{ new: true }
170178
);
171179
if (!result) {
172-
return res.status(404).json({ message: 'Survey not found' });
180+
return res
181+
.status(404)
182+
.json({
183+
message:
184+
'Survey not found or you do not have permission to update this survey'
185+
});
173186
}
174187
res.status(200).json({
175188
message: 'Survey updated successfully',
@@ -202,8 +215,14 @@ router.post(
202215
'/',
203216
[auth, validate(createSurveySchema)],
204217
async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
205-
if (!req.authorization?.can(ACTIONS.CASL.CREATE, SUBJECTS.SURVEY)) {
206-
return res.sendStatus(403);
218+
// Different permissions based on creation with or without referral
219+
// Check permission based on the action type based on query param `new`
220+
let createActionType = ACTIONS.CASL.CREATE;
221+
if (req.query.new === 'true') {
222+
createActionType = ACTIONS.CUSTOM.CREATE_WITHOUT_REFERRAL;
223+
}
224+
if (!req.authorization?.can(createActionType, SUBJECTS.USER)) {
225+
return res.status(403).json({ message: 'Forbidden' });
207226
}
208227
try {
209228
const surveyData: ISurvey = req.body;
@@ -280,6 +299,11 @@ router.delete(
280299
'/:objectId',
281300
[auth],
282301
async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
302+
// TODO: may want to revisit here and see if we need to make a distinction for
303+
// hard/soft-delete in permissions
304+
if (!req.authorization?.can(ACTIONS.CASL.DELETE, SUBJECTS.SURVEY)) {
305+
return res.sendStatus(403);
306+
}
283307
try {
284308
// TODO: Delete specific fields within survey document as well
285309
const result = await Survey.findByIdAndUpdate(

server/src/routes/v2/users.ts

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { subject } from '@casl/ability';
12
import { accessibleBy } from '@casl/mongoose';
23
import express, { NextFunction, Response } from 'express';
34

@@ -32,15 +33,16 @@ const router = express.Router();
3233
*/
3334
router.get(
3435
'/',
35-
[auth], // TODO: add `read_users` permission check
36+
[auth],
3637
async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
38+
if (!req.authorization?.can(ACTIONS.CASL.READ, SUBJECTS.USER)) {
39+
return res.sendStatus(403);
40+
}
3741
try {
3842
const result = await User.find({
3943
$and: [
4044
req.query,
41-
req?.authorization
42-
? accessibleBy(req.authorization).ofType(User.modelName)
43-
: {},
45+
accessibleBy(req.authorization).ofType(User.modelName),
4446
{ deletedAt: null }
4547
]
4648
});
@@ -83,8 +85,16 @@ router.get(
8385
*/
8486
router.get(
8587
'/:objectId',
86-
[auth], // TODO: add `read_users` permission check
88+
[auth],
8789
async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
90+
if (
91+
!req.authorization?.can(
92+
ACTIONS.CASL.READ,
93+
subject(SUBJECTS.USER, { _id: req.params.objectId })
94+
)
95+
) {
96+
return res.status(403).json({ message: 'Forbidden' });
97+
}
8898
try {
8999
const result = await User.findById(req.params.objectId);
90100
if (!result) {
@@ -123,8 +133,11 @@ router.get(
123133
*/
124134
router.post(
125135
'/',
126-
[auth, validate(createUserSchema)], // TODO: add `create_users` permission check
136+
[auth, validate(createUserSchema)],
127137
async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
138+
if (!req.authorization?.can(ACTIONS.CASL.CREATE, SUBJECTS.USER)) {
139+
return res.status(403).json({ message: 'Forbidden' });
140+
}
128141
try {
129142
const result = await User.create(req.body);
130143
res.status(201).json({
@@ -167,9 +180,47 @@ router.post(
167180
*/
168181
router.patch(
169182
'/:objectId',
170-
[auth, validate(updateUserSchema)], // TODO: add `update_users` permission check
183+
[auth, validate(updateUserSchema)],
171184
async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
185+
// Basic check if permissible _id to update for user
186+
if (
187+
!req.authorization?.can(
188+
ACTIONS.CASL.UPDATE,
189+
subject(SUBJECTS.USER, { _id: req.params.objectId })
190+
)
191+
) {
192+
return res.status(403).json({ message: 'Forbidden' });
193+
}
172194
try {
195+
// Update access if document-dependent AND field-dependent (i.e. fields in our request body)
196+
// so we first need to fetch the document and check permissions (more below)
197+
const user = await User.findById(req.params.objectId);
198+
if (!user) {
199+
return res.status(404).json({ message: 'User not found' });
200+
}
201+
202+
// Check if we are allowed to update all fields in request body
203+
// These checks depend on interactions between our current user's role and location and the target user's role and location
204+
// ex. a manager can only approve users with 'VOLUNTEER' role at their current location today, but are also allowed to update their own profile
205+
// if the request tries to update a field that is impermissible for the document we fetched, we will return a 403 error
206+
if (
207+
!Object.keys(req.body).every(field =>
208+
req.authorization?.can(
209+
ACTIONS.CASL.UPDATE,
210+
subject(SUBJECTS.USER, user.toObject()),
211+
field
212+
)
213+
)
214+
) {
215+
return res
216+
.status(403)
217+
.json({
218+
message:
219+
'You are not allowed to make this update on this user'
220+
});
221+
}
222+
223+
// Passed permission checks, update user document
173224
const result = await User.findByIdAndUpdate(
174225
req.params.objectId,
175226
req.body,
@@ -216,8 +267,16 @@ router.patch(
216267
*/
217268
router.delete(
218269
'/:objectId',
219-
[auth], // TODO: add `delete_users` permission check
270+
[auth],
220271
async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
272+
if (
273+
!req.authorization?.can(
274+
ACTIONS.CASL.DELETE,
275+
subject(SUBJECTS.USER, { _id: req.params.objectId })
276+
)
277+
) {
278+
return res.status(403).json({ message: 'Forbidden' });
279+
}
221280
try {
222281
const result = await User.findByIdAndUpdate(
223282
req.params.objectId,

0 commit comments

Comments
 (0)