Implement permission management and enhance user interface for permissions in the application

Add new permission routes and integrate permission checks across various existing routes to ensure proper access control. Update the UserClub model to include role and permissions fields, allowing for more granular user access management. Enhance the frontend by introducing a user dropdown menu for managing permissions and displaying relevant options based on user roles. Improve the overall user experience by implementing permission-based visibility for navigation links and actions throughout the application.
This commit is contained in:
Torsten Schulz (local)
2025-10-17 09:44:10 +02:00
parent 2dd5e28cbc
commit 56f0ce2f27
31 changed files with 2854 additions and 92 deletions

View File

@@ -1,5 +1,6 @@
import express from 'express';
import { authenticate } from '../middleware/authMiddleware.js';
import { authorize } from '../middleware/authorizationMiddleware.js';
import { getClubs, addClub, getClub, requestClubAccess, getPendingApprovals, approveClubAccess, rejectClubAccess } from '../controllers/clubsController.js';
const router = express.Router();
@@ -8,8 +9,8 @@ router.get('/', authenticate, getClubs);
router.post('/', authenticate, addClub);
router.get('/:clubid', authenticate, getClub);
router.get('/request/:clubid', authenticate, requestClubAccess);
router.get('/pending/:clubid', authenticate, getPendingApprovals);
router.post('/approve', authenticate, approveClubAccess);
router.post('/reject', authenticate, rejectClubAccess);
router.get('/pending/:clubid', authenticate, authorize('approvals', 'read'), getPendingApprovals);
router.post('/approve', authenticate, authorize('approvals', 'write'), approveClubAccess);
router.post('/reject', authenticate, authorize('approvals', 'write'), rejectClubAccess);
export default router;

View File

@@ -1,5 +1,6 @@
import express from 'express';
import { authenticate } from '../middleware/authMiddleware.js';
import { authorize } from '../middleware/authorizationMiddleware.js';
import {
getDatesForClub,
createDateForClub,
@@ -14,14 +15,14 @@ import {
const router = express.Router();
router.post('/note', authenticate, addDiaryNote);
router.delete('/note/:noteId', authenticate, deleteDiaryNote);
router.post('/tag', authenticate, addDiaryTag);
router.post('/tag/:clubId/add-tag', authenticate, addTagToDiaryDate);
router.delete('/:clubId/tag', authenticate, deleteTagFromDiaryDate);
router.get('/:clubId', authenticate, getDatesForClub);
router.post('/:clubId', authenticate, createDateForClub);
router.put('/:clubId', authenticate, updateTrainingTimes);
router.delete('/:clubId/:dateId', authenticate, deleteDateForClub);
router.post('/note', authenticate, authorize('diary', 'write'), addDiaryNote);
router.delete('/note/:noteId', authenticate, authorize('diary', 'delete'), deleteDiaryNote);
router.post('/tag', authenticate, authorize('diary', 'write'), addDiaryTag);
router.post('/tag/:clubId/add-tag', authenticate, authorize('diary', 'write'), addTagToDiaryDate);
router.delete('/:clubId/tag', authenticate, authorize('diary', 'delete'), deleteTagFromDiaryDate);
router.get('/:clubId', authenticate, authorize('diary', 'read'), getDatesForClub);
router.post('/:clubId', authenticate, authorize('diary', 'write'), createDateForClub);
router.put('/:clubId', authenticate, authorize('diary', 'write'), updateTrainingTimes);
router.delete('/:clubId/:dateId', authenticate, authorize('diary', 'delete'), deleteDateForClub);
export default router;

View File

@@ -1,20 +1,21 @@
import express from 'express';
import { uploadCSV, getLeaguesForCurrentSeason, getMatchesForLeagues, getMatchesForLeague, getLeagueTable, fetchLeagueTableFromMyTischtennis, updateMatchPlayers, getPlayerMatchStats } from '../controllers/matchController.js';
import { authenticate } from '../middleware/authMiddleware.js';
import { authorize } from '../middleware/authorizationMiddleware.js';
import multer from 'multer';
const router = express.Router();
const upload = multer({ dest: 'uploads/' });
router.post('/import', authenticate, upload.single('file'), uploadCSV);
router.get('/leagues/current/:clubId', authenticate, getLeaguesForCurrentSeason);
router.get('/leagues/:clubId/matches/:leagueId', authenticate, getMatchesForLeague);
router.get('/leagues/:clubId/matches', authenticate, getMatchesForLeagues);
router.get('/leagues/:clubId/table/:leagueId', authenticate, getLeagueTable);
router.post('/leagues/:clubId/table/:leagueId/fetch', authenticate, fetchLeagueTableFromMyTischtennis);
router.patch('/:matchId/players', authenticate, updateMatchPlayers);
router.get('/leagues/:clubId/stats/:leagueId', authenticate, getPlayerMatchStats);
router.post('/import', authenticate, authorize('schedule', 'write'), upload.single('file'), uploadCSV);
router.get('/leagues/current/:clubId', authenticate, authorize('schedule', 'read'), getLeaguesForCurrentSeason);
router.get('/leagues/:clubId/matches/:leagueId', authenticate, authorize('schedule', 'read'), getMatchesForLeague);
router.get('/leagues/:clubId/matches', authenticate, authorize('schedule', 'read'), getMatchesForLeagues);
router.get('/leagues/:clubId/table/:leagueId', authenticate, authorize('schedule', 'read'), getLeagueTable);
router.post('/leagues/:clubId/table/:leagueId/fetch', authenticate, authorize('mytischtennis', 'write'), fetchLeagueTableFromMyTischtennis);
router.patch('/:matchId/players', authenticate, authorize('schedule', 'write'), updateMatchPlayers);
router.get('/leagues/:clubId/stats/:leagueId', authenticate, authorize('schedule', 'read'), getPlayerMatchStats);
export default router;

View File

@@ -1,6 +1,7 @@
import { getClubMembers, getWaitingApprovals, setClubMembers, uploadMemberImage, getMemberImage, updateRatingsFromMyTischtennis, rotateMemberImage } from '../controllers/memberController.js';
import express from 'express';
import { authenticate } from '../middleware/authMiddleware.js';
import { authorize } from '../middleware/authorizationMiddleware.js';
import multer from 'multer';
const router = express.Router();
@@ -8,12 +9,12 @@ const router = express.Router();
const storage = multer.memoryStorage();
const upload = multer({ storage: storage });
router.post('/image/:clubId/:memberId', authenticate, upload.single('image'), uploadMemberImage);
router.get('/image/:clubId/:memberId', authenticate, getMemberImage);
router.get('/get/:id/:showAll', authenticate, getClubMembers);
router.post('/set/:id', authenticate, setClubMembers);
router.get('/notapproved/:id', authenticate, getWaitingApprovals);
router.post('/update-ratings/:id', authenticate, updateRatingsFromMyTischtennis);
router.post('/rotate-image/:clubId/:memberId', authenticate, rotateMemberImage);
router.post('/image/:clubId/:memberId', authenticate, authorize('members', 'write'), upload.single('image'), uploadMemberImage);
router.get('/image/:clubId/:memberId', authenticate, authorize('members', 'read'), getMemberImage);
router.get('/get/:id/:showAll', authenticate, authorize('members', 'read'), getClubMembers);
router.post('/set/:id', authenticate, authorize('members', 'write'), setClubMembers);
router.get('/notapproved/:id', authenticate, authorize('members', 'read'), getWaitingApprovals);
router.post('/update-ratings/:id', authenticate, authorize('mytischtennis', 'write'), updateRatingsFromMyTischtennis);
router.post('/rotate-image/:clubId/:memberId', authenticate, authorize('members', 'write'), rotateMemberImage);
export default router;

View File

@@ -2,23 +2,24 @@ import express from 'express';
import myTischtennisController from '../controllers/myTischtennisController.js';
import myTischtennisUrlController from '../controllers/myTischtennisUrlController.js';
import { authenticate } from '../middleware/authMiddleware.js';
import { authorize } from '../middleware/authorizationMiddleware.js';
const router = express.Router();
// All routes require authentication
router.use(authenticate);
// GET /api/mytischtennis/account - Get account
// GET /api/mytischtennis/account - Get account (alle dürfen lesen)
router.get('/account', myTischtennisController.getAccount);
// GET /api/mytischtennis/status - Check status
// GET /api/mytischtennis/status - Check status (alle dürfen lesen)
router.get('/status', myTischtennisController.getStatus);
// POST /api/mytischtennis/account - Create or update account
// POST /api/mytischtennis/account - Create or update account (alle dürfen bearbeiten)
router.post('/account', myTischtennisController.upsertAccount);
// DELETE /api/mytischtennis/account - Delete account
router.delete('/account', myTischtennisController.deleteAccount);
// DELETE /api/mytischtennis/account - Delete account (nur Admin)
router.delete('/account', authorize('mytischtennis_admin', 'write'), myTischtennisController.deleteAccount);
// POST /api/mytischtennis/verify - Verify login
router.post('/verify', myTischtennisController.verifyLogin);

View File

@@ -0,0 +1,30 @@
import express from 'express';
import { authenticate } from '../middleware/authMiddleware.js';
import { authorize, requireAdmin } from '../middleware/authorizationMiddleware.js';
import permissionController from '../controllers/permissionController.js';
const router = express.Router();
// Get available roles (no club context needed)
router.get('/roles/available', authenticate, permissionController.getAvailableRoles);
// Get permission structure (no club context needed)
router.get('/structure/all', authenticate, permissionController.getPermissionStructure);
// Get current user's permissions for a club (no authorization check - needed to load permissions)
router.get('/:clubId', authenticate, permissionController.getUserPermissions);
// Get all club members with their permissions (admin only)
router.get('/:clubId/members', authenticate, authorize('permissions', 'read'), permissionController.getClubMembersWithPermissions);
// Update user role (admin only)
router.put('/:clubId/user/:userId/role', authenticate, authorize('permissions', 'write'), permissionController.updateUserRole);
// Update user permissions (admin only)
router.put('/:clubId/user/:userId/permissions', authenticate, authorize('permissions', 'write'), permissionController.updateUserPermissions);
// Update user status (admin only)
router.put('/:clubId/user/:userId/status', authenticate, authorize('permissions', 'write'), permissionController.updateUserStatus);
export default router;

View File

@@ -10,6 +10,7 @@ import {
} from '../controllers/predefinedActivityController.js';
import multer from 'multer';
import { authenticate } from '../middleware/authMiddleware.js';
import { authorize } from '../middleware/authorizationMiddleware.js';
import { uploadPredefinedActivityImage, deletePredefinedActivityImage } from '../controllers/predefinedActivityImageController.js';
import PredefinedActivityImage from '../models/PredefinedActivityImage.js';
import path from 'path';
@@ -18,16 +19,16 @@ import fs from 'fs';
const router = express.Router();
const upload = multer({ storage: multer.memoryStorage() });
router.post('/', authenticate, createPredefinedActivity);
router.get('/', authenticate, getAllPredefinedActivities);
router.get('/:id', authenticate, getPredefinedActivityById);
router.put('/:id', authenticate, updatePredefinedActivity);
router.post('/:id/image', authenticate, upload.single('image'), uploadPredefinedActivityImage);
router.put('/:id/image', authenticate, upload.single('image'), uploadPredefinedActivityImage);
router.delete('/:id/image/:imageId', authenticate, deletePredefinedActivityImage);
router.get('/search/query', authenticate, searchPredefinedActivities);
router.post('/merge', authenticate, mergePredefinedActivities);
router.post('/deduplicate', authenticate, deduplicatePredefinedActivities);
router.post('/', authenticate, authorize('predefined_activities', 'write'), createPredefinedActivity);
router.get('/', authenticate, authorize('predefined_activities', 'read'), getAllPredefinedActivities);
router.get('/:id', authenticate, authorize('predefined_activities', 'read'), getPredefinedActivityById);
router.put('/:id', authenticate, authorize('predefined_activities', 'write'), updatePredefinedActivity);
router.post('/:id/image', authenticate, authorize('predefined_activities', 'write'), upload.single('image'), uploadPredefinedActivityImage);
router.put('/:id/image', authenticate, authorize('predefined_activities', 'write'), upload.single('image'), uploadPredefinedActivityImage);
router.delete('/:id/image/:imageId', authenticate, authorize('predefined_activities', 'delete'), deletePredefinedActivityImage);
router.get('/search/query', authenticate, authorize('predefined_activities', 'read'), searchPredefinedActivities);
router.post('/merge', authenticate, authorize('predefined_activities', 'write'), mergePredefinedActivities);
router.post('/deduplicate', authenticate, authorize('predefined_activities', 'write'), deduplicatePredefinedActivities);
router.get('/:id/image/:imageId', async (req, res) => {
try {
const { id, imageId } = req.params;

View File

@@ -1,5 +1,6 @@
import express from 'express';
import { authenticate } from '../middleware/authMiddleware.js';
import { authorize } from '../middleware/authorizationMiddleware.js';
import {
getTeams,
getTeam,
@@ -12,21 +13,21 @@ import {
const router = express.Router();
// Get all teams for a club
router.get('/club/:clubid', authenticate, getTeams);
router.get('/club/:clubid', authenticate, authorize('teams', 'read'), getTeams);
// Get leagues for a club
router.get('/leagues/:clubid', authenticate, getLeagues);
router.get('/leagues/:clubid', authenticate, authorize('teams', 'read'), getLeagues);
// Get a specific team
router.get('/:teamid', authenticate, getTeam);
router.get('/:teamid', authenticate, authorize('teams', 'read'), getTeam);
// Create a new team
router.post('/club/:clubid', authenticate, createTeam);
router.post('/club/:clubid', authenticate, authorize('teams', 'write'), createTeam);
// Update a team
router.put('/:teamid', authenticate, updateTeam);
router.put('/:teamid', authenticate, authorize('teams', 'write'), updateTeam);
// Delete a team
router.delete('/:teamid', authenticate, deleteTeam);
router.delete('/:teamid', authenticate, authorize('teams', 'delete'), deleteTeam);
export default router;