|
1 | | -import { subject } from '@casl/ability'; |
2 | 1 | import { accessibleBy } from '@casl/mongoose'; |
3 | 2 | import express, { NextFunction, Response } from 'express'; |
4 | 3 |
|
@@ -40,15 +39,14 @@ router.get( |
40 | 39 | '/', |
41 | 40 | [auth], |
42 | 41 | async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { |
| 42 | + if (!req.authorization?.can(ACTIONS.CASL.READ, SUBJECTS.SURVEY)) { |
| 43 | + return res.sendStatus(403); |
| 44 | + } |
43 | 45 | try { |
44 | 46 | const result = await Survey.find({ |
45 | 47 | $and: [ |
46 | 48 | req.query, |
47 | | - req?.authorization |
48 | | - ? accessibleBy(req.authorization).ofType( |
49 | | - Survey.modelName |
50 | | - ) |
51 | | - : {}, |
| 49 | + accessibleBy(req.authorization).ofType(Survey.modelName), |
52 | 50 | { deletedAt: null } |
53 | 51 | ] |
54 | 52 | }); |
@@ -94,22 +92,26 @@ router.get( |
94 | 92 | '/:objectId', |
95 | 93 | [auth], |
96 | 94 | 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)) { |
103 | 97 | return res.sendStatus(403); |
104 | 98 | } |
105 | 99 | try { |
106 | 100 | 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 | + ] |
109 | 106 | }); |
110 | 107 | // Survey not found |
111 | 108 | 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 | + }); |
113 | 115 | } |
114 | 116 | // Successfully fetched survey |
115 | 117 | res.status(200).json({ |
@@ -154,22 +156,33 @@ router.patch( |
154 | 156 | '/:objectId', |
155 | 157 | [auth, validate(updateSurveySchema)], |
156 | 158 | 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)) { |
163 | 161 | return res.sendStatus(403); |
164 | 162 | } |
165 | 163 | try { |
| 164 | + // Update access is document-dependent but not field-dependent, so we only need to pass in |
| 165 | + // an accessibleBy filter here |
166 | 166 | 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 | + }, |
168 | 176 | req.body, |
169 | 177 | { new: true } |
170 | 178 | ); |
171 | 179 | 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 | + }); |
173 | 186 | } |
174 | 187 | res.status(200).json({ |
175 | 188 | message: 'Survey updated successfully', |
@@ -202,8 +215,14 @@ router.post( |
202 | 215 | '/', |
203 | 216 | [auth, validate(createSurveySchema)], |
204 | 217 | 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' }); |
207 | 226 | } |
208 | 227 | try { |
209 | 228 | const surveyData: ISurvey = req.body; |
@@ -280,6 +299,11 @@ router.delete( |
280 | 299 | '/:objectId', |
281 | 300 | [auth], |
282 | 301 | 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 | + } |
283 | 307 | try { |
284 | 308 | // TODO: Delete specific fields within survey document as well |
285 | 309 | const result = await Survey.findByIdAndUpdate( |
|
0 commit comments