From 23edfe3dfc4f7822fed51f17739113d6f4e8a770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rnyi=20M=C3=A1rk?= Date: Tue, 31 Mar 2026 23:33:27 +0200 Subject: [PATCH] fix: harden permissions system after code review - Gate permissions in /app-config behind optionalAuth so unauthenticated requests don't receive admin configuration - Fix trip_delete isMember parameter (was hardcoded false) - Return skipped keys from savePermissions for admin visibility - Add disabled prop to CustomSelect, use in BudgetPanel currency picker - Fix CollabChat reaction handler returning false instead of void - Pass canUploadFiles as prop to NoteFormModal instead of internal store read - Make edit-only NoteFormModal props optional (onDeleteFile, note, tripId) - Add missing trailing newlines to .gitignore and it.ts --- .gitignore | 2 +- client/src/components/Budget/BudgetPanel.tsx | 3 ++- client/src/components/Collab/CollabChat.tsx | 2 +- client/src/components/Collab/CollabNotes.tsx | 15 ++++++++------- client/src/components/shared/CustomSelect.tsx | 10 +++++++--- client/src/i18n/translations/it.ts | 2 +- server/src/routes/admin.ts | 4 ++-- server/src/routes/auth.ts | 8 ++++---- server/src/routes/trips.ts | 3 ++- server/src/services/permissions.ts | 10 +++++++--- 10 files changed, 35 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index 57a3d9e..24ca73e 100644 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,4 @@ coverage .eslintcache .cache *.tsbuildinfo -*.tgz \ No newline at end of file +*.tgz diff --git a/client/src/components/Budget/BudgetPanel.tsx b/client/src/components/Budget/BudgetPanel.tsx index 349af9c..e95d990 100644 --- a/client/src/components/Budget/BudgetPanel.tsx +++ b/client/src/components/Budget/BudgetPanel.tsx @@ -649,7 +649,8 @@ export default function BudgetPanel({ tripId, tripMembers = [] }: BudgetPanelPro
{}} + onChange={setCurrency} + disabled={!canEdit} options={CURRENCIES.map(c => ({ value: c, label: `${c} (${SYMBOLS[c] || c})` }))} searchable /> diff --git a/client/src/components/Collab/CollabChat.tsx b/client/src/components/Collab/CollabChat.tsx index 2afa755..251a443 100644 --- a/client/src/components/Collab/CollabChat.tsx +++ b/client/src/components/Collab/CollabChat.tsx @@ -740,7 +740,7 @@ export default function CollabChat({ tripId, currentUser }: CollabChatProps) { {msg.reactions.map(r => { const myReaction = r.users.some(u => String(u.user_id) === String(currentUser.id)) return ( - canEdit && handleReact(msg.id, r.emoji)} /> + { if (canEdit) handleReact(msg.id, r.emoji) }} /> ) })}
diff --git a/client/src/components/Collab/CollabNotes.tsx b/client/src/components/Collab/CollabNotes.tsx index ccf6b7b..bee4e0d 100644 --- a/client/src/components/Collab/CollabNotes.tsx +++ b/client/src/components/Collab/CollabNotes.tsx @@ -218,19 +218,17 @@ function UserAvatar({ user, size = 14 }: UserAvatarProps) { interface NoteFormModalProps { onClose: () => void onSubmit: (data: { title: string; content: string; category: string; website: string; files?: File[] }) => Promise - onDeleteFile: (noteId: number, fileId: number) => Promise + onDeleteFile?: (noteId: number, fileId: number) => Promise existingCategories: string[] categoryColors: Record getCategoryColor: (category: string) => string - note: CollabNote | null - tripId: number + note?: CollabNote | null + tripId?: number t: (key: string) => string + canUploadFiles?: boolean } -function NoteFormModal({ onClose, onSubmit, onDeleteFile, existingCategories, categoryColors, getCategoryColor, note, tripId, t }: NoteFormModalProps) { - const can = useCanDo() - const tripObj = useTripStore((s) => s.trip) - const canUploadFiles = can('file_upload', tripObj) +function NoteFormModal({ onClose, onSubmit, onDeleteFile, existingCategories, categoryColors, getCategoryColor, note, tripId, t, canUploadFiles = true }: NoteFormModalProps) { const isEdit = !!note const allCategories = [...new Set([...existingCategories, ...Object.keys(categoryColors || {})])].filter(Boolean) @@ -889,6 +887,7 @@ export default function CollabNotes({ tripId, currentUser }: CollabNotesProps) { const can = useCanDo() const trip = useTripStore((s) => s.trip) const canEdit = can('collab_edit', trip) + const canUploadFiles = can('file_upload', trip) const [notes, setNotes] = useState([]) const [loading, setLoading] = useState(true) const [showNewModal, setShowNewModal] = useState(false) @@ -1343,6 +1342,7 @@ export default function CollabNotes({ tripId, currentUser }: CollabNotesProps) { existingCategories={categories} categoryColors={categoryColors} getCategoryColor={getCategoryColor} + canUploadFiles={canUploadFiles} t={t} /> )} @@ -1358,6 +1358,7 @@ export default function CollabNotes({ tripId, currentUser }: CollabNotesProps) { existingCategories={categories} categoryColors={categoryColors} getCategoryColor={getCategoryColor} + canUploadFiles={canUploadFiles} t={t} /> )} diff --git a/client/src/components/shared/CustomSelect.tsx b/client/src/components/shared/CustomSelect.tsx index 2df9c5e..2526c8a 100644 --- a/client/src/components/shared/CustomSelect.tsx +++ b/client/src/components/shared/CustomSelect.tsx @@ -19,6 +19,7 @@ interface CustomSelectProps { searchable?: boolean style?: React.CSSProperties size?: 'sm' | 'md' + disabled?: boolean } export default function CustomSelect({ @@ -29,6 +30,7 @@ export default function CustomSelect({ searchable = false, style = {}, size = 'md', + disabled = false, }: CustomSelectProps) { const [open, setOpen] = useState(false) const [search, setSearch] = useState('') @@ -83,17 +85,19 @@ export default function CustomSelect({ {/* Trigger */}