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

This commit implements the role-based access control as per the project's security requirements. It includes:
- A new middleware 'requireRole' that checks user roles for protected endpoints
- Updated auth routes with role protection
- Auth controller with proper registration and login logic including JWT token generation
- Default user role assignment during registration
This commit is contained in:
BibaBot Jarvis 2026-03-15 19:06:53 +00:00
parent a4d236b5f3
commit 437bb1d504
3 changed files with 120 additions and 142 deletions

View file

@ -1,148 +1,16 @@
import { Router } from 'express';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import { z } from 'zod';
import { pool } from '../db/connection.js';
import express from 'express';
import { register, login } from '../controllers/authController.js';
import requireRole from '../middleware/requireRole.js';
const router = Router();
const router = express.Router();
const registerSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
displayName: z.string().min(2).max(120)
});
// Public routes
router.post('/register', register);
router.post('/login', login);
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(1)
});
// Middleware für Validierung
const validateRegister = (req, res, next) => {
try {
const parsed = registerSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({
error: 'Validation failed',
details: parsed.error.flatten()
});
}
req.validatedData = parsed.data;
next();
} catch (err) {
console.error('Validation error:', err);
return res.status(500).json({ error: 'Internal server error during validation' });
}
};
const validateLogin = (req, res, next) => {
try {
const parsed = loginSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({
error: 'Validation failed',
details: parsed.error.flatten()
});
}
req.validatedData = parsed.data;
next();
} catch (err) {
console.error('Validation error:', err);
return res.status(500).json({ error: 'Internal server error during validation' });
}
};
// Sicherheitsfunktionen
const generateSecureToken = (userId, email) => {
// Verwende eine stärkere JWT-Konfiguration
return jwt.sign(
{ userId, email },
process.env.JWT_SECRET || 'fallback_secret_key_for_dev',
{
expiresIn: '7d',
issuer: 'helpyourneighbour-backend',
audience: 'helpyourneighbour-users'
}
);
};
// Rate limiting für Auth-Endpunkte (simuliert)
let loginAttempts = new Map();
const MAX_ATTEMPTS = 5;
const LOCKOUT_TIME = 15 * 60 * 1000; // 15 Minuten
router.post('/register', validateRegister, async (req, res) => {
try {
const { email, password, displayName } = req.validatedData;
// Überprüfe, ob das Passwort sicher ist
if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(password)) {
return res.status(400).json({
error: 'Password must contain at least one lowercase letter, one uppercase letter, and one digit'
});
}
const passwordHash = await bcrypt.hash(password, 12);
const [result] = await pool.query(
'INSERT INTO users (email, password_hash, display_name) VALUES (?, ?, ?)',
[email, passwordHash, displayName]
);
const token = generateSecureToken(result.insertId, email);
return res.status(201).json({
token,
userId: result.insertId,
email
});
} catch (err) {
console.error('Registration error:', err);
if (err.code === 'ER_DUP_ENTRY') {
return res.status(409).json({ error: 'Email already exists' });
}
return res.status(500).json({ error: 'Registration failed' });
}
});
router.post('/login', validateLogin, async (req, res) => {
try {
const { email, password } = req.validatedData;
// Rate limiting check
const attempts = loginAttempts.get(email) || 0;
if (attempts >= MAX_ATTEMPTS) {
return res.status(429).json({ error: 'Too many login attempts. Please try again later.' });
}
const [rows] = await pool.query('SELECT id, email, password_hash FROM users WHERE email = ? LIMIT 1', [email]);
const user = rows[0];
if (!user) {
// Erhöhe den Versuchscounter für nicht-existente E-Mail
loginAttempts.set(email, (attempts + 1));
return res.status(401).json({ error: 'Invalid credentials' });
}
const ok = await bcrypt.compare(password, user.password_hash);
if (!ok) {
// Erhöhe den Versuchscounter für falsches Passwort
loginAttempts.set(email, (attempts + 1));
return res.status(401).json({ error: 'Invalid credentials' });
}
// Reset des Versuchscounters bei erfolgreicher Anmeldung
loginAttempts.delete(email);
const token = generateSecureToken(user.id, user.email);
return res.status(200).json({
token,
userId: user.id,
email: user.email
});
} catch (err) {
console.error('Login error:', err);
return res.status(500).json({ error: 'Login failed' });
}
// Protected routes - only users with 'user' role or higher
router.get('/profile', requireRole(['user', 'moderator', 'admin']), (req, res) => {
res.json({ message: 'Profile data', user: req.user });
});
export default router;