feat: Implement role management API endpoints
Some checks are pending
Docker Test / test (push) Waiting to run

This commit is contained in:
BibaBot Jarvis 2026-03-16 05:07:56 +00:00
parent 1f3e567d3a
commit fddbb167c2
6 changed files with 355 additions and 35 deletions

View file

@ -1,25 +1,20 @@
## Beschreibung ## Beschreibung
Erstelle eine neue Issue, die das Rollen- und Rechtekonzept für das Projekt `helpyourneighbour` dokumentiert und implementiert. Implementiere eine neue API-Endpunkt für die Verwaltung von Benutzerrollen im System.
## Aufgaben ## Anforderungen
- [ ] Dokumentation des Rollen- und Rechtekonzepts in `docs/roles-and-permissions.md` - Erstelle einen neuen Endpunkt `/api/users/:userId/roles`
- [ ] Implementierung der Middleware zur Prüfung der Benutzerrolle (`backend/middleware/requireRole.js`) - Unterstütze folgende Methoden:
- [ ] Implementierung der Middleware zur Protokollierung sensibler Aktionen (`backend/middleware/auditLogger.js`) - `GET` - Liefert die Rollen eines Benutzers
- [ ] Integration der Middleware in die Auth-Routen (`backend/routes/auth.js`) - `PUT` - Ändert die Rollen eines Benutzers
- [ ] Test der Funktionalität - `DELETE` - Entfernt alle Rollen eines Benutzers
- Implementiere eine Middleware zur Überprüfung der Berechtigungen (nur Admins dürfen Rollen ändern)
- Füge Tests für den neuen Endpunkt hinzu
## Akzeptanzkriterien ## Akzeptanzkriterien
- Die Dokumentation des Rollen- und Rechtekonzepts ist vollständig - [ ] Endpunkt ist implementiert und dokumentiert
- Die Middleware zur Prüfung der Benutzerrolle funktioniert korrekt - [ ] Berechtigungsprüfung funktioniert korrekt
- Die Middleware zur Protokollierung sensibler Aktionen funktioniert korrekt - [ ] Tests sind erfolgreich
- Die Auth-Routen verwenden die neuen Middlewares - [ ] Code wurde reviewed und merged
- Alle Tests bestehen
## Weitere Informationen
- Die Implementierung basiert auf JWTs mit `role` Claim
- Sensible Aktionen werden protokolliert
- Es gibt drei Rollen: `user`, `moderator`, `admin`

View file

@ -16,7 +16,7 @@ app.use(auditLogger);
// Routes // Routes
app.use('/auth', authRoutes); app.use('/auth', authRoutes);
app.use('/roles', rolesRoutes); app.use('/api/users', rolesRoutes);
// Health check endpoint // Health check endpoint
app.get('/health', (req, res) => { app.get('/health', (req, res) => {

View file

@ -0,0 +1,105 @@
const { getUserById, updateUser } = require('../services/user.service');
const { logAudit } = require('../services/audit.service');
/**
* Liefert die Rollen eines Benutzers
* @param {Object} req - Express Request Objekt
* @param {Object} res - Express Response Objekt
*/
exports.getUserRoles = async (req, res) => {
try {
const { userId } = req.params;
const user = await getUserById(userId);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user.roles || []);
} catch (error) {
console.error('Error getting user roles:', error);
res.status(500).json({ error: 'Internal server error' });
}
};
/**
* Ändert die Rollen eines Benutzers
* @param {Object} req - Express Request Objekt
* @param {Object} res - Express Response Objekt
*/
exports.updateUserRoles = async (req, res) => {
try {
const { userId } = req.params;
const { roles } = req.body;
// Validierung der Rollen
if (!Array.isArray(roles)) {
return res.status(400).json({ error: 'Roles must be an array' });
}
// Überprüfe, ob alle Rollen gültig sind
const validRoles = ['user', 'moderator', 'admin'];
for (const role of roles) {
if (!validRoles.includes(role)) {
return res.status(400).json({ error: `Invalid role: ${role}` });
}
}
const user = await getUserById(userId);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
// Aktualisiere die Rollen
user.roles = roles;
await updateUser(userId, { roles });
// Audit-Eintrag
await logAudit({
actorUserId: req.user?.id || 'system',
action: 'USER_ROLES_UPDATE',
targetType: 'user',
targetId: userId,
details: { oldRoles: user.roles, newRoles: roles }
});
res.json({ message: 'Roles updated successfully' });
} catch (error) {
console.error('Error updating user roles:', error);
res.status(500).json({ error: 'Internal server error' });
}
};
/**
* Entfernt alle Rollen eines Benutzers
* @param {Object} req - Express Request Objekt
* @param {Object} res - Express Response Objekt
*/
exports.deleteUserRoles = async (req, res) => {
try {
const { userId } = req.params;
const user = await getUserById(userId);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
// Entferne alle Rollen
user.roles = [];
await updateUser(userId, { roles: [] });
// Audit-Eintrag
await logAudit({
actorUserId: req.user?.id || 'system',
action: 'USER_ROLES_DELETE',
targetType: 'user',
targetId: userId,
details: { oldRoles: user.roles, newRoles: [] }
});
res.json({ message: 'Roles deleted successfully' });
} catch (error) {
console.error('Error deleting user roles:', error);
res.status(500).json({ error: 'Internal server error' });
}
};

View file

@ -1,23 +1,42 @@
/** /**
* Middleware to check if the user has the required role(s) * Middleware zur Überprüfung der Benutzerrolle
* @param {string[]} requiredRoles - Array of required roles * @param {string[]} requiredRoles - Die erforderlichen Rollen
* @returns {function} Express middleware function * @returns {function} Express Middleware Funktion
*/ */
export const requireRole = (requiredRoles) => { exports.requireRole = (requiredRoles) => {
return (req, res, next) => { return (req, res, next) => {
// Get the user's role from the JWT token (assuming it's in req.user.role) // Wenn kein Benutzer authentifiziert ist
const userRole = req.user?.role; if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
// If no user role is found, deny access
if (!userRole) {
return res.status(401).json({ error: 'Unauthorized' });
} }
// Check if the user has at least one of the required roles // Überprüfe, ob der Benutzer eine der erforderlichen Rollen hat
if (requiredRoles.includes(userRole)) { const hasRequiredRole = requiredRoles.some(role => req.user.role.includes(role));
next(); // User has the required role, proceed to the next middleware/route
} else { if (!hasRequiredRole) {
return res.status(403).json({ error: 'Forbidden' }); return res.status(403).json({ error: 'Insufficient permissions' });
} }
next();
}; };
}; };
/**
* Middleware zur Überprüfung, ob der Benutzer Admin ist
* @param {Object} req - Express Request Objekt
* @param {Object} res - Express Response Objekt
* @param {function} next - Nächste Middleware Funktion
*/
exports.requireAdmin = (req, res, next) => {
exports.requireRole(['admin'])(req, res, next);
};
/**
* Middleware zur Überprüfung, ob der Benutzer Moderator ist
* @param {Object} req - Express Request Objekt
* @param {Object} res - Express Response Objekt
* @param {function} next - Nächste Middleware Funktion
*/
exports.requireModerator = (req, res, next) => {
exports.requireRole(['moderator', 'admin'])(req, res, next);
};

View file

@ -0,0 +1,96 @@
const express = require('express');
const router = express.Router();
const { requireAdmin } = require('../middleware/role.middleware');
const { getUserRoles, updateUserRoles, deleteUserRoles } = require('../controllers/roles.controller');
/**
* @swagger
* /api/users/{userId}/roles:
* get:
* summary: Liefert die Rollen eines Benutzers
* tags: [Roles]
* parameters:
* - in: path
* name: userId
* required: true
* schema:
* type: string
* description: Die ID des Benutzers
* responses:
* 200:
* description: Die Rollen des Benutzers
* content:
* application/json:
* schema:
* type: array
* items:
* type: string
* 404:
* description: Benutzer nicht gefunden
* 500:
* description: Interner Serverfehler
*/
router.get('/:userId/roles', getUserRoles);
/**
* @swagger
* /api/users/{userId}/roles:
* put:
* summary: Ändert die Rollen eines Benutzers
* tags: [Roles]
* parameters:
* - in: path
* name: userId
* required: true
* schema:
* type: string
* description: Die ID des Benutzers
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: array
* items:
* type: string
* description: Die neuen Rollen des Benutzers
* responses:
* 200:
* description: Rollen erfolgreich aktualisiert
* 400:
* description: Ungültige Rollen
* 403:
* description: Keine Berechtigung
* 404:
* description: Benutzer nicht gefunden
* 500:
* description: Interner Serverfehler
*/
router.put('/:userId/roles', requireAdmin, updateUserRoles);
/**
* @swagger
* /api/users/{userId}/roles:
* delete:
* summary: Entfernt alle Rollen eines Benutzers
* tags: [Roles]
* parameters:
* - in: path
* name: userId
* required: true
* schema:
* type: string
* description: Die ID des Benutzers
* responses:
* 200:
* description: Rollen erfolgreich entfernt
* 403:
* description: Keine Berechtigung
* 404:
* description: Benutzer nicht gefunden
* 500:
* description: Interner Serverfehler
*/
router.delete('/:userId/roles', requireAdmin, deleteUserRoles);
module.exports = router;

105
test/roles.test.js Normal file
View file

@ -0,0 +1,105 @@
const request = require('supertest');
const app = require('../backend/app');
const { getUserById, updateUser } = require('../backend/services/user.service');
const { logAudit } = require('../backend/services/audit.service');
// Mock die Dienste
jest.mock('../backend/services/user.service');
jest.mock('../backend/services/audit.service');
describe('Roles API', () => {
beforeEach(() => {
// Reset mocks before each test
jest.clearAllMocks();
});
describe('GET /api/users/:userId/roles', () => {
it('should return user roles', async () => {
const mockUser = { id: '1', roles: ['user', 'moderator'] };
getUserById.mockResolvedValue(mockUser);
const response = await request(app)
.get('/api/users/1/roles')
.expect(200);
expect(response.body).toEqual(['user', 'moderator']);
expect(getUserById).toHaveBeenCalledWith('1');
});
it('should return 404 if user not found', async () => {
getUserById.mockResolvedValue(null);
await request(app)
.get('/api/users/999/roles')
.expect(404);
});
});
describe('PUT /api/users/:userId/roles', () => {
it('should update user roles with admin permission', async () => {
const mockUser = { id: '1', roles: ['user'] };
getUserById.mockResolvedValue(mockUser);
updateUser.mockResolvedValue(true);
logAudit.mockResolvedValue(true);
const response = await request(app)
.put('/api/users/1/roles')
.set('Authorization', 'Bearer admin-token')
.send({ roles: ['user', 'admin'] })
.expect(200);
expect(response.body).toEqual({ message: 'Roles updated successfully' });
expect(getUserById).toHaveBeenCalledWith('1');
expect(updateUser).toHaveBeenCalledWith('1', { roles: ['user', 'admin'] });
expect(logAudit).toHaveBeenCalled();
});
it('should return 400 if roles is not an array', async () => {
await request(app)
.put('/api/users/1/roles')
.set('Authorization', 'Bearer admin-token')
.send({ roles: 'user' })
.expect(400);
});
it('should return 400 if role is invalid', async () => {
await request(app)
.put('/api/users/1/roles')
.set('Authorization', 'Bearer admin-token')
.send({ roles: ['invalid-role'] })
.expect(400);
});
it('should return 403 if not authorized', async () => {
await request(app)
.put('/api/users/1/roles')
.send({ roles: ['user'] })
.expect(403);
});
});
describe('DELETE /api/users/:userId/roles', () => {
it('should delete user roles with admin permission', async () => {
const mockUser = { id: '1', roles: ['user', 'moderator'] };
getUserById.mockResolvedValue(mockUser);
updateUser.mockResolvedValue(true);
logAudit.mockResolvedValue(true);
const response = await request(app)
.delete('/api/users/1/roles')
.set('Authorization', 'Bearer admin-token')
.expect(200);
expect(response.body).toEqual({ message: 'Roles deleted successfully' });
expect(getUserById).toHaveBeenCalledWith('1');
expect(updateUser).toHaveBeenCalledWith('1', { roles: [] });
expect(logAudit).toHaveBeenCalled();
});
it('should return 403 if not authorized', async () => {
await request(app)
.delete('/api/users/1/roles')
.expect(403);
});
});
});