fix permissions

This commit is contained in:
Matthias Hochmeister
2026-03-25 08:42:45 +01:00
parent 43b7093996
commit eb92dfcc96
10 changed files with 26 additions and 32 deletions

View File

@@ -226,8 +226,8 @@ class BookingController {
const isOwner = booking.gebucht_von === req.user!.id; const isOwner = booking.gebucht_von === req.user!.id;
const groups: string[] = req.user?.groups ?? []; const groups: string[] = req.user?.groups ?? [];
const isAdmin = groups.includes('dashboard_admin'); const isAdmin = groups.includes('dashboard_admin');
const canCancelOwn = isAdmin || permissionService.hasPermission(groups, 'kalender:cancel_own_bookings'); const canCancelOwn = isAdmin || permissionService.hasPermission(groups, 'kalender:manage_bookings');
const canCancelAny = isAdmin || permissionService.hasPermission(groups, 'kalender:delete_bookings'); const canCancelAny = isAdmin || permissionService.hasPermission(groups, 'kalender:manage_bookings');
if (!(isOwner && canCancelOwn) && !canCancelAny) { if (!(isOwner && canCancelOwn) && !canCancelAny) {
res.status(403).json({ success: false, message: 'Keine Berechtigung' }); res.status(403).json({ success: false, message: 'Keine Berechtigung' });

View File

@@ -5,14 +5,14 @@ import { requirePermission } from '../middleware/rbac.middleware';
const router = Router(); const router = Router();
// ── Read-only (any authenticated user) ─────────────────────────────────────── // ── Read-only ────────────────────────────────────────────────────────────────
router.get('/', authenticate, atemschutzController.list.bind(atemschutzController)); router.get('/', authenticate, requirePermission('atemschutz:view'), atemschutzController.list.bind(atemschutzController));
router.get('/stats', authenticate, atemschutzController.getStats.bind(atemschutzController)); router.get('/stats', authenticate, requirePermission('atemschutz:view'), atemschutzController.getStats.bind(atemschutzController));
router.get('/expiring', authenticate, atemschutzController.getExpiring.bind(atemschutzController)); router.get('/expiring', authenticate, requirePermission('atemschutz:view'), atemschutzController.getExpiring.bind(atemschutzController));
router.get('/my-status', authenticate, atemschutzController.getMyStatus.bind(atemschutzController)); router.get('/my-status', authenticate, atemschutzController.getMyStatus.bind(atemschutzController));
router.get('/user/:userId', authenticate, atemschutzController.getByUserId.bind(atemschutzController)); router.get('/user/:userId', authenticate, requirePermission('atemschutz:view'), atemschutzController.getByUserId.bind(atemschutzController));
router.get('/:id', authenticate, atemschutzController.getOne.bind(atemschutzController)); router.get('/:id', authenticate, requirePermission('atemschutz:view'), atemschutzController.getOne.bind(atemschutzController));
// ── Write — gruppenfuehrer+ ───────────────────────────────────────────────── // ── Write — gruppenfuehrer+ ─────────────────────────────────────────────────

View File

@@ -88,7 +88,7 @@ router.delete(
router.patch( router.patch(
'/:id/status', '/:id/status',
authenticate, authenticate,
requirePermission('bestellungen:create'), requirePermission('bestellungen:manage_orders'),
bestellungController.updateStatus.bind(bestellungController) bestellungController.updateStatus.bind(bestellungController)
); );

View File

@@ -42,7 +42,7 @@ router.get(
*/ */
router.post( router.post(
'/refresh-stats', '/refresh-stats',
requirePermission('einsaetze:delete'), requirePermission('einsaetze:create'),
incidentController.refreshStats.bind(incidentController) incidentController.refreshStats.bind(incidentController)
); );

View File

@@ -9,7 +9,7 @@ const router = Router();
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// injectTeilnahmenFlag // injectTeilnahmenFlag
// //
// Sets req.canSeeTeilnahmen = true for users with kalender:mark_attendance. // Sets req.canSeeTeilnahmen = true for users with kalender:create.
// Regular Mitglieder see only attendance counts; officers see the full list. // Regular Mitglieder see only attendance counts; officers see the full list.
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -23,7 +23,7 @@ async function injectTeilnahmenFlag(
const groups: string[] = req.user?.groups ?? []; const groups: string[] = req.user?.groups ?? [];
(req as any).canSeeTeilnahmen = (req as any).canSeeTeilnahmen =
groups.includes('dashboard_admin') || groups.includes('dashboard_admin') ||
permissionService.hasPermission(groups, 'kalender:mark_attendance'); permissionService.hasPermission(groups, 'kalender:create');
} }
} catch (_err) { } catch (_err) {
// Non-fatal — default to restricted view // Non-fatal — default to restricted view
@@ -67,12 +67,12 @@ router.get('/calendar-token', authenticate, trainingController.getCalendarToken)
/** /**
* GET /api/training/stats?year=<YYYY> * GET /api/training/stats?year=<YYYY>
* Annual participation stats. * Annual participation stats.
* Requires Kommandant or above (requirePermission('kalender:view_reports')). * Requires Kommandant or above (requirePermission('kalender:create')).
*/ */
router.get( router.get(
'/stats', '/stats',
authenticate, authenticate,
requirePermission('kalender:view_reports'), requirePermission('kalender:create'),
trainingController.getStats trainingController.getStats
); );
@@ -120,7 +120,7 @@ router.patch(
router.delete( router.delete(
'/:id', '/:id',
authenticate, authenticate,
requirePermission('kalender:cancel'), requirePermission('kalender:create'),
trainingController.cancelEvent trainingController.cancelEvent
); );
@@ -141,7 +141,7 @@ router.patch(
router.post( router.post(
'/:id/attendance/mark', '/:id/attendance/mark',
authenticate, authenticate,
requirePermission('kalender:mark_attendance'), requirePermission('kalender:create'),
trainingController.markAttendance trainingController.markAttendance
); );

View File

@@ -14,14 +14,8 @@ const DEFAULT_GROUP_HIERARCHY: Record<string, string[]> = {
const DEFAULT_PERMISSION_DEPS: Record<string, string[]> = { const DEFAULT_PERMISSION_DEPS: Record<string, string[]> = {
'kalender:create': ['kalender:view'], 'kalender:create': ['kalender:view'],
'kalender:cancel': ['kalender:view'], 'kalender:view_bookings': ['kalender:view'],
'kalender:mark_attendance': ['kalender:view'], 'kalender:manage_bookings': ['kalender:view', 'kalender:view_bookings'],
'kalender:create_bookings': ['kalender:view'],
'kalender:edit_bookings': ['kalender:view', 'kalender:create_bookings'],
'kalender:cancel_own_bookings': ['kalender:view'],
'kalender:delete_bookings': ['kalender:view', 'kalender:edit_bookings'],
'kalender:manage_categories': ['kalender:view'],
'kalender:view_reports': ['kalender:view'],
'kalender:widget_events': ['kalender:view'], 'kalender:widget_events': ['kalender:view'],
'kalender:widget_bookings': ['kalender:view'], 'kalender:widget_bookings': ['kalender:view'],
'kalender:widget_quick_add': ['kalender:view', 'kalender:create'], 'kalender:widget_quick_add': ['kalender:view', 'kalender:create'],

View File

@@ -112,7 +112,7 @@ export default function BestellungDetail() {
const erinnerungen = data?.erinnerungen ?? []; const erinnerungen = data?.erinnerungen ?? [];
const historie = data?.historie ?? []; const historie = data?.historie ?? [];
const canEdit = hasPermission('bestellungen:edit'); const canEdit = hasPermission('bestellungen:create');
const nextStatus = bestellung ? getNextStatus(bestellung.status) : null; const nextStatus = bestellung ? getNextStatus(bestellung.status) : null;
// ── Mutations ── // ── Mutations ──

View File

@@ -96,10 +96,10 @@ function FahrzeugBuchungen() {
const { user } = useAuth(); const { user } = useAuth();
const { hasPermission } = usePermissionContext(); const { hasPermission } = usePermissionContext();
const notification = useNotification(); const notification = useNotification();
const canCreate = hasPermission('kalender:create_bookings'); const canCreate = hasPermission('kalender:manage_bookings');
const canWrite = hasPermission('kalender:edit_bookings'); const canWrite = hasPermission('kalender:manage_bookings');
const canCancelOwn = hasPermission('kalender:cancel_own_bookings'); const canCancelOwn = hasPermission('kalender:manage_bookings');
const canChangeBuchungsArt = hasPermission('kalender:manage_categories'); const canChangeBuchungsArt = hasPermission('kalender:create');
// ── Week navigation ──────────────────────────────────────────────────────── // ── Week navigation ────────────────────────────────────────────────────────
const [currentWeekStart, setCurrentWeekStart] = useState<Date>(() => const [currentWeekStart, setCurrentWeekStart] = useState<Date>(() =>

View File

@@ -1746,8 +1746,8 @@ export default function Kalender() {
const isMobile = useMediaQuery(theme.breakpoints.down('sm')); const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
const canWriteEvents = hasPermission('kalender:create'); const canWriteEvents = hasPermission('kalender:create');
const canWriteBookings = hasPermission('kalender:edit_bookings'); const canWriteBookings = hasPermission('kalender:manage_bookings');
const canCreateBookings = hasPermission('kalender:create_bookings'); const canCreateBookings = hasPermission('kalender:manage_bookings');
// ── Tab ───────────────────────────────────────────────────────────────────── // ── Tab ─────────────────────────────────────────────────────────────────────
const [activeTab, setActiveTab] = useState(() => { const [activeTab, setActiveTab] = useState(() => {

View File

@@ -272,7 +272,7 @@ export default function UebungDetail() {
const isMobile = useMediaQuery(theme.breakpoints.down('sm')); const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
const canWrite = hasPermission('kalender:create'); const canWrite = hasPermission('kalender:create');
const canSeeAttendees = hasPermission('kalender:mark_attendance'); const canSeeAttendees = hasPermission('kalender:create');
const [markAttendanceOpen, setMarkAttendanceOpen] = useState(false); const [markAttendanceOpen, setMarkAttendanceOpen] = useState(false);
const [rsvpLoading, setRsvpLoading] = useState<'zugesagt' | 'abgesagt' | null>(null); const [rsvpLoading, setRsvpLoading] = useState<'zugesagt' | 'abgesagt' | null>(null);