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
|
||||
|
||||
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`
|
||||
- [ ] Implementierung der Middleware zur Prüfung der Benutzerrolle (`backend/middleware/requireRole.js`)
|
||||
- [ ] Implementierung der Middleware zur Protokollierung sensibler Aktionen (`backend/middleware/auditLogger.js`)
|
||||
- [ ] Integration der Middleware in die Auth-Routen (`backend/routes/auth.js`)
|
||||
- [ ] Test der Funktionalität
|
||||
- Erstelle einen neuen Endpunkt `/api/users/:userId/roles`
|
||||
- Unterstütze folgende Methoden:
|
||||
- `GET` - Liefert die Rollen eines Benutzers
|
||||
- `PUT` - Ändert die Rollen eines Benutzers
|
||||
- `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
|
||||
|
||||
- Die Dokumentation des Rollen- und Rechtekonzepts ist vollständig
|
||||
- Die Middleware zur Prüfung der Benutzerrolle funktioniert korrekt
|
||||
- Die Middleware zur Protokollierung sensibler Aktionen funktioniert korrekt
|
||||
- Die Auth-Routen verwenden die neuen Middlewares
|
||||
- Alle Tests bestehen
|
||||
|
||||
## Weitere Informationen
|
||||
|
||||
- Die Implementierung basiert auf JWTs mit `role` Claim
|
||||
- Sensible Aktionen werden protokolliert
|
||||
- Es gibt drei Rollen: `user`, `moderator`, `admin`
|
||||
- [ ] Endpunkt ist implementiert und dokumentiert
|
||||
- [ ] Berechtigungsprüfung funktioniert korrekt
|
||||
- [ ] Tests sind erfolgreich
|
||||
- [ ] Code wurde reviewed und merged
|
||||
|
|
@ -16,7 +16,7 @@ app.use(auditLogger);
|
|||
|
||||
// Routes
|
||||
app.use('/auth', authRoutes);
|
||||
app.use('/roles', rolesRoutes);
|
||||
app.use('/api/users', rolesRoutes);
|
||||
|
||||
// Health check endpoint
|
||||
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)
|
||||
* @param {string[]} requiredRoles - Array of required roles
|
||||
* @returns {function} Express middleware function
|
||||
* Middleware zur Überprüfung der Benutzerrolle
|
||||
* @param {string[]} requiredRoles - Die erforderlichen Rollen
|
||||
* @returns {function} Express Middleware Funktion
|
||||
*/
|
||||
export const requireRole = (requiredRoles) => {
|
||||
exports.requireRole = (requiredRoles) => {
|
||||
return (req, res, next) => {
|
||||
// Get the user's role from the JWT token (assuming it's in req.user.role)
|
||||
const userRole = req.user?.role;
|
||||
|
||||
// If no user role is found, deny access
|
||||
if (!userRole) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
// Wenn kein Benutzer authentifiziert ist
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Authentication required' });
|
||||
}
|
||||
|
||||
// Check if the user has at least one of the required roles
|
||||
if (requiredRoles.includes(userRole)) {
|
||||
next(); // User has the required role, proceed to the next middleware/route
|
||||
} else {
|
||||
return res.status(403).json({ error: 'Forbidden' });
|
||||
|
||||
// Überprüfe, ob der Benutzer eine der erforderlichen Rollen hat
|
||||
const hasRequiredRole = requiredRoles.some(role => req.user.role.includes(role));
|
||||
|
||||
if (!hasRequiredRole) {
|
||||
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