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:
Gérnyi Márk
2026-03-31 23:45:11 +02:00
parent 23edfe3dfc
commit 1fbc19ad4f
3 changed files with 15 additions and 2 deletions

View File

@@ -206,8 +206,8 @@ router.post('/notes/:id/files', authenticate, noteUpload.single('file'), (req: R
const { tripId, id } = req.params;
const access = verifyTripAccess(Number(tripId), authReq.user.id);
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))
return res.status(403).json({ error: 'No permission' });
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 to upload files' });
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);

View File

@@ -226,6 +226,8 @@ router.patch('/:id/star', authenticate, (req: Request, res: Response) => {
const trip = verifyTripOwnership(tripId, authReq.user.id);
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;
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);
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;
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);
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;
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);
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[];
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);
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);
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);
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);
res.json({ success: true });