feat: Implement role management API endpoints
Some checks are pending
Docker Test / test (push) Waiting to run
Some checks are pending
Docker Test / test (push) Waiting to run
This commit is contained in:
parent
1f3e567d3a
commit
fddbb167c2
6 changed files with 355 additions and 35 deletions
|
|
@ -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`
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
105
backend/controllers/roles.controller.js
Normal file
105
backend/controllers/roles.controller.js
Normal 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' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -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);
|
||||||
};
|
};
|
||||||
96
backend/routes/roles.routes.js
Normal file
96
backend/routes/roles.routes.js
Normal 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
105
test/roles.test.js
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue