fix: add missing permission checks to file routes and map context menu
- Add checkPermission to 6 unprotected file endpoints (star, restore, permanent delete, empty trash, link, unlink) - Gate map right-click place creation with place_edit permission - Use file_upload permission for collab note file uploads
This commit is contained in:
@@ -169,6 +169,7 @@ export default function TripPlannerPage(): React.ReactElement | null {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handleMapContextMenu = useCallback(async (e) => {
|
const handleMapContextMenu = useCallback(async (e) => {
|
||||||
|
if (!can('place_edit', trip)) return
|
||||||
e.originalEvent?.preventDefault()
|
e.originalEvent?.preventDefault()
|
||||||
const { lat, lng } = e.latlng
|
const { lat, lng } = e.latlng
|
||||||
setPrefillCoords({ lat, lng })
|
setPrefillCoords({ lat, lng })
|
||||||
|
|||||||
@@ -206,8 +206,8 @@ router.post('/notes/:id/files', authenticate, noteUpload.single('file'), (req: R
|
|||||||
const { tripId, id } = req.params;
|
const { tripId, id } = req.params;
|
||||||
const access = verifyTripAccess(Number(tripId), authReq.user.id);
|
const access = verifyTripAccess(Number(tripId), authReq.user.id);
|
||||||
if (!access) return res.status(404).json({ error: 'Trip not found' });
|
if (!access) return res.status(404).json({ error: 'Trip not found' });
|
||||||
if (!checkPermission('collab_edit', authReq.user.role, access.user_id, authReq.user.id, access.user_id !== authReq.user.id))
|
if (!checkPermission('file_upload', authReq.user.role, access.user_id, authReq.user.id, access.user_id !== authReq.user.id))
|
||||||
return res.status(403).json({ error: 'No permission' });
|
return res.status(403).json({ error: 'No permission to upload files' });
|
||||||
if (!req.file) return res.status(400).json({ error: 'No file uploaded' });
|
if (!req.file) return res.status(400).json({ error: 'No file uploaded' });
|
||||||
|
|
||||||
const note = db.prepare('SELECT id FROM collab_notes WHERE id = ? AND trip_id = ?').get(id, tripId);
|
const note = db.prepare('SELECT id FROM collab_notes WHERE id = ? AND trip_id = ?').get(id, tripId);
|
||||||
|
|||||||
@@ -226,6 +226,8 @@ router.patch('/:id/star', authenticate, (req: Request, res: Response) => {
|
|||||||
|
|
||||||
const trip = verifyTripOwnership(tripId, authReq.user.id);
|
const trip = verifyTripOwnership(tripId, authReq.user.id);
|
||||||
if (!trip) return res.status(404).json({ error: 'Trip not found' });
|
if (!trip) return res.status(404).json({ error: 'Trip not found' });
|
||||||
|
if (!checkPermission('file_edit', authReq.user.role, trip.user_id, authReq.user.id, trip.user_id !== authReq.user.id))
|
||||||
|
return res.status(403).json({ error: 'No permission' });
|
||||||
|
|
||||||
const file = db.prepare('SELECT * FROM trip_files WHERE id = ? AND trip_id = ?').get(id, tripId) as TripFile | undefined;
|
const file = db.prepare('SELECT * FROM trip_files WHERE id = ? AND trip_id = ?').get(id, tripId) as TripFile | undefined;
|
||||||
if (!file) return res.status(404).json({ error: 'File not found' });
|
if (!file) return res.status(404).json({ error: 'File not found' });
|
||||||
@@ -263,6 +265,8 @@ router.post('/:id/restore', authenticate, (req: Request, res: Response) => {
|
|||||||
|
|
||||||
const trip = verifyTripOwnership(tripId, authReq.user.id);
|
const trip = verifyTripOwnership(tripId, authReq.user.id);
|
||||||
if (!trip) return res.status(404).json({ error: 'Trip not found' });
|
if (!trip) return res.status(404).json({ error: 'Trip not found' });
|
||||||
|
if (!checkPermission('file_delete', authReq.user.role, trip.user_id, authReq.user.id, trip.user_id !== authReq.user.id))
|
||||||
|
return res.status(403).json({ error: 'No permission' });
|
||||||
|
|
||||||
const file = db.prepare('SELECT * FROM trip_files WHERE id = ? AND trip_id = ? AND deleted_at IS NOT NULL').get(id, tripId) as TripFile | undefined;
|
const file = db.prepare('SELECT * FROM trip_files WHERE id = ? AND trip_id = ? AND deleted_at IS NOT NULL').get(id, tripId) as TripFile | undefined;
|
||||||
if (!file) return res.status(404).json({ error: 'File not found in trash' });
|
if (!file) return res.status(404).json({ error: 'File not found in trash' });
|
||||||
@@ -281,6 +285,8 @@ router.delete('/:id/permanent', authenticate, (req: Request, res: Response) => {
|
|||||||
|
|
||||||
const trip = verifyTripOwnership(tripId, authReq.user.id);
|
const trip = verifyTripOwnership(tripId, authReq.user.id);
|
||||||
if (!trip) return res.status(404).json({ error: 'Trip not found' });
|
if (!trip) return res.status(404).json({ error: 'Trip not found' });
|
||||||
|
if (!checkPermission('file_delete', authReq.user.role, trip.user_id, authReq.user.id, trip.user_id !== authReq.user.id))
|
||||||
|
return res.status(403).json({ error: 'No permission' });
|
||||||
|
|
||||||
const file = db.prepare('SELECT * FROM trip_files WHERE id = ? AND trip_id = ? AND deleted_at IS NOT NULL').get(id, tripId) as TripFile | undefined;
|
const file = db.prepare('SELECT * FROM trip_files WHERE id = ? AND trip_id = ? AND deleted_at IS NOT NULL').get(id, tripId) as TripFile | undefined;
|
||||||
if (!file) return res.status(404).json({ error: 'File not found in trash' });
|
if (!file) return res.status(404).json({ error: 'File not found in trash' });
|
||||||
@@ -302,6 +308,8 @@ router.delete('/trash/empty', authenticate, (req: Request, res: Response) => {
|
|||||||
|
|
||||||
const trip = verifyTripOwnership(tripId, authReq.user.id);
|
const trip = verifyTripOwnership(tripId, authReq.user.id);
|
||||||
if (!trip) return res.status(404).json({ error: 'Trip not found' });
|
if (!trip) return res.status(404).json({ error: 'Trip not found' });
|
||||||
|
if (!checkPermission('file_delete', authReq.user.role, trip.user_id, authReq.user.id, trip.user_id !== authReq.user.id))
|
||||||
|
return res.status(403).json({ error: 'No permission' });
|
||||||
|
|
||||||
const trashed = db.prepare('SELECT * FROM trip_files WHERE trip_id = ? AND deleted_at IS NOT NULL').all(tripId) as TripFile[];
|
const trashed = db.prepare('SELECT * FROM trip_files WHERE trip_id = ? AND deleted_at IS NOT NULL').all(tripId) as TripFile[];
|
||||||
for (const file of trashed) {
|
for (const file of trashed) {
|
||||||
@@ -323,6 +331,8 @@ router.post('/:id/link', authenticate, (req: Request, res: Response) => {
|
|||||||
|
|
||||||
const trip = verifyTripOwnership(tripId, authReq.user.id);
|
const trip = verifyTripOwnership(tripId, authReq.user.id);
|
||||||
if (!trip) return res.status(404).json({ error: 'Trip not found' });
|
if (!trip) return res.status(404).json({ error: 'Trip not found' });
|
||||||
|
if (!checkPermission('file_edit', authReq.user.role, trip.user_id, authReq.user.id, trip.user_id !== authReq.user.id))
|
||||||
|
return res.status(403).json({ error: 'No permission' });
|
||||||
|
|
||||||
const file = db.prepare('SELECT * FROM trip_files WHERE id = ? AND trip_id = ?').get(id, tripId);
|
const file = db.prepare('SELECT * FROM trip_files WHERE id = ? AND trip_id = ?').get(id, tripId);
|
||||||
if (!file) return res.status(404).json({ error: 'File not found' });
|
if (!file) return res.status(404).json({ error: 'File not found' });
|
||||||
@@ -346,6 +356,8 @@ router.delete('/:id/link/:linkId', authenticate, (req: Request, res: Response) =
|
|||||||
|
|
||||||
const trip = verifyTripOwnership(tripId, authReq.user.id);
|
const trip = verifyTripOwnership(tripId, authReq.user.id);
|
||||||
if (!trip) return res.status(404).json({ error: 'Trip not found' });
|
if (!trip) return res.status(404).json({ error: 'Trip not found' });
|
||||||
|
if (!checkPermission('file_edit', authReq.user.role, trip.user_id, authReq.user.id, trip.user_id !== authReq.user.id))
|
||||||
|
return res.status(403).json({ error: 'No permission' });
|
||||||
|
|
||||||
db.prepare('DELETE FROM file_links WHERE id = ? AND file_id = ?').run(linkId, id);
|
db.prepare('DELETE FROM file_links WHERE id = ? AND file_id = ?').run(linkId, id);
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
|
|||||||
Reference in New Issue
Block a user