import { Router } from 'express'; import bcrypt from 'bcryptjs'; import jwt from 'jsonwebtoken'; import { z } from 'zod'; import { pool } from '../db/connection.js'; const router = Router(); const registerSchema = z.object({ email: z.string().email(), password: z.string().min(8), displayName: z.string().min(2).max(120) }); 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' }); } }); export default router;