feat: implement role-based access control and auth routes
Some checks are pending
Docker Test / test (push) Waiting to run

This commit is contained in:
BibaBot Jarvis 2026-03-16 00:07:16 +00:00
parent 91eb0828e3
commit 7c9862a08a
3 changed files with 344 additions and 45 deletions

View file

@ -1,28 +1,33 @@
// middleware/auditLogger.js // middleware/auditLogger.js
const fs = require('fs').promises; const db = require('../db');
const path = require('path');
// In a real app, this would write to a database /**
async function auditLogger(req, res, next) { * Middleware zur Protokollierung sensibler Aktionen
const logEntry = { * @param {string} action - Name der Aktion (z.B. 'USER_SUSPEND')
timestamp: new Date().toISOString(), * @param {string} targetType - Typ des Zielobjekts (z.B. 'user')
actorUserId: req.user?.id || 'anonymous', * @returns {function} Express-Middleware-Funktion
action: `${req.method} ${req.path}`, */
targetType: req.route?.path || 'unknown', function auditLogger(action, targetType) {
targetId: req.params?.id || 'unknown', return async (req, res, next) => {
userAgent: req.get('User-Agent'), try {
ip: req.ip const timestamp = new Date().toISOString();
const actorUserId = req.user?.id || null;
const targetId = req.params.id || req.body.id || null;
const reason = req.body.reason || null;
// Audit-Eintrag in die Datenbank schreiben
await db.run(
'INSERT INTO audit_log (timestamp, actor_user_id, action, target_type, target_id, reason) VALUES (?, ?, ?, ?, ?, ?)',
[timestamp, actorUserId, action, targetType, targetId, reason]
);
next();
} catch (err) {
console.error('Audit logging failed:', err);
// Fehler bei Audit-Logging sollte nicht den Request blockieren
next();
}
}; };
// Log to file (in real app, this would be a DB insert)
try {
const logPath = path.join(__dirname, '../logs/audit.log');
await fs.appendFile(logPath, JSON.stringify(logEntry) + '\n');
} catch (error) {
console.error('Failed to write audit log:', error);
}
next();
} }
module.exports = auditLogger; module.exports = auditLogger;

View file

@ -1,18 +1,30 @@
// middleware/requireRole.js // middleware/requireRole.js
const requireRole = (allowedRoles) => { const jwt = require('jsonwebtoken');
/**
* Middleware zur Prüfung der Benutzerrolle
* @param {string[]} allowedRoles - Erlaubte Rollen
* @returns {function} Express-Middleware-Funktion
*/
function requireRole(allowedRoles) {
return (req, res, next) => { return (req, res, next) => {
const userRole = req.user?.role; const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
if (!userRole) { return res.status(401).json({ error: 'Authorization header missing or invalid' });
return res.status(401).json({ error: 'Authorization required' });
} }
if (!allowedRoles.includes(userRole)) { const token = authHeader.substring(7); // "Bearer " entfernen
return res.status(403).json({ error: 'Insufficient permissions' }); try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
if (!decoded.role || !allowedRoles.includes(decoded.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
req.user = decoded; // Nutzerdaten an die Request-Objekt anhängen
next();
} catch (err) {
return res.status(401).json({ error: 'Invalid token' });
} }
next();
}; };
}; }
module.exports = requireRole; module.exports = requireRole;

View file

@ -1,27 +1,309 @@
// routes/auth.js // routes/auth.js
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const { authenticateUser } = require('../middleware/auth'); const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const db = require('../db');
const requireRole = require('../middleware/requireRole'); const requireRole = require('../middleware/requireRole');
// Public route - register /**
* @swagger
* /auth/register:
* post:
* summary: Registriert einen neuen Benutzer
* tags: [Auth]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - username
* - email
* - password
* properties:
* username:
* type: string
* email:
* type: string
* password:
* type: string
* responses:
* 201:
* description: Benutzer erfolgreich registriert
* content:
* application/json:
* schema:
* type: object
* properties:
* user:
* type: object
* properties:
* id:
* type: integer
* username:
* type: string
* email:
* type: string
* role:
* type: string
* token:
* type: string
* 400:
* description: Ungültige Eingabedaten
* 409:
* description: Benutzer existiert bereits
*/
router.post('/register', async (req, res) => { router.post('/register', async (req, res) => {
// Implementation for user registration const { username, email, password } = req.body;
if (!username || !email || !password) {
return res.status(400).json({ error: 'Missing required fields' });
}
try {
// Prüfen, ob Benutzer bereits existiert
const existingUser = await db.get('SELECT id FROM users WHERE email = ?', [email]);
if (existingUser) {
return res.status(409).json({ error: 'User already exists' });
}
// Passwort hashen
const hashedPassword = await bcrypt.hash(password, 10);
// Neuen Benutzer anlegen
const result = await db.run(
'INSERT INTO users (username, email, password_hash, role) VALUES (?, ?, ?, ?)',
[username, email, hashedPassword, 'user']
);
// Token generieren
const token = jwt.sign(
{ id: result.lastID, username, email, role: 'user' },
process.env.JWT_SECRET,
{ expiresIn: '24h' }
);
res.status(201).json({
user: {
id: result.lastID,
username,
email,
role: 'user'
},
token
});
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Internal server error' });
}
}); });
// Public route - login /**
router.post('/login', authenticateUser, (req, res) => { * @swagger
// Implementation for user login * /auth/login:
* post:
* summary: Loggt einen Benutzer ein
* tags: [Auth]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - email
* - password
* properties:
* email:
* type: string
* password:
* type: string
* responses:
* 200:
* description: Login erfolgreich
* content:
* application/json:
* schema:
* type: object
* properties:
* user:
* type: object
* properties:
* id:
* type: integer
* username:
* type: string
* email:
* type: string
* role:
* type: string
* token:
* type: string
* 400:
* description: Ungültige Eingabedaten
* 401:
* description: Falsche Anmeldedaten
*/
router.post('/login', async (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({ error: 'Missing email or password' });
}
try {
const user = await db.get('SELECT id, username, email, password_hash, role FROM users WHERE email = ?', [email]);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const isValidPassword = await bcrypt.compare(password, user.password_hash);
if (!isValidPassword) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Token generieren
const token = jwt.sign(
{ id: user.id, username: user.username, email: user.email, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '24h' }
);
res.json({
user: {
id: user.id,
username: user.username,
email: user.email,
role: user.role
},
token
});
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Internal server error' });
}
}); });
// Protected route - get user profile /**
router.get('/profile', requireRole(['user', 'moderator', 'admin']), (req, res) => { * @swagger
// Implementation for getting user profile * /auth/profile:
* get:
* summary: Holt das Profil des eingeloggten Benutzers
* tags: [Auth]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: Profil erfolgreich abgerufen
* content:
* application/json:
* schema:
* type: object
* properties:
* user:
* type: object
* properties:
* id:
* type: integer
* username:
* type: string
* email:
* type: string
* role:
* type: string
* 401:
* description: Nicht autorisiert
*/
router.get('/profile', requireRole(['user', 'moderator', 'admin']), async (req, res) => {
try {
const user = await db.get('SELECT id, username, email, role FROM users WHERE id = ?', [req.user.id]);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json({ user });
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Internal server error' });
}
}); });
// Protected route - update user profile /**
router.put('/profile', requireRole(['user', 'moderator', 'admin']), (req, res) => { * @swagger
// Implementation for updating user profile * /auth/profile:
* put:
* summary: Aktualisiert das Profil des eingeloggten Benutzers
* tags: [Auth]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* username:
* type: string
* email:
* type: string
* responses:
* 200:
* description: Profil erfolgreich aktualisiert
* content:
* application/json:
* schema:
* type: object
* properties:
* user:
* type: object
* properties:
* id:
* type: integer
* username:
* type: string
* email:
* type: string
* role:
* type: string
* 400:
* description: Ungültige Eingabedaten
* 401:
* description: Nicht autorisiert
*/
router.put('/profile', requireRole(['user', 'moderator', 'admin']), async (req, res) => {
const { username, email } = req.body;
if (!username && !email) {
return res.status(400).json({ error: 'At least one field must be provided' });
}
try {
let updateFields = [];
let values = [];
if (username) {
updateFields.push('username = ?');
values.push(username);
}
if (email) {
updateFields.push('email = ?');
values.push(email);
}
values.push(req.user.id);
const query = `UPDATE users SET ${updateFields.join(', ')} WHERE id = ?`;
await db.run(query, values);
// Aktualisierte Daten abrufen
const updatedUser = await db.get('SELECT id, username, email, role FROM users WHERE id = ?', [req.user.id]);
res.json({ user: updatedUser });
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Internal server error' });
}
}); });
module.exports = router; module.exports = router;