const bcrypt = require('bcryptjs'); const { User, PasswordResetToken } = require('../models'); const jwt = require('jsonwebtoken'); const { addTokenToBlacklist } = require('../utils/blacklist'); const { transporter, getPasswordResetEmailTemplate } = require('../config/email'); const crypto = require('crypto'); function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } exports.register = async (req, res) => { const { name, email, password } = req.body; if (!name || !email || !password) { return res.status(400).json({ message: 'Alle Felder sind erforderlich' }); } try { const hashedPassword = await bcrypt.hash(password, 10); console.log('Register: creating user', { email }); const maxAttempts = 3; let attempt = 0; let createdUser = null; let lastError = null; while (attempt < maxAttempts && !createdUser) { try { createdUser = await User.create({ name, email, password: hashedPassword, active: false }); } catch (err) { lastError = err; // Spezifisch auf Lock-Timeout reagieren und erneut versuchen if ((err.code === 'ER_LOCK_WAIT_TIMEOUT' || err?.parent?.code === 'ER_LOCK_WAIT_TIMEOUT') && attempt < maxAttempts - 1) { const backoffMs = 300 * (attempt + 1); console.warn(`Register: ER_LOCK_WAIT_TIMEOUT, retry in ${backoffMs}ms (attempt ${attempt + 1}/${maxAttempts})`); await delay(backoffMs); attempt++; continue; } throw err; } } if (!createdUser && lastError) { console.error('Register error (after retries):', lastError); return res.status(503).json({ message: 'Zeitüberschreitung beim Zugriff auf die Datenbank. Bitte erneut versuchen.' }); } console.log('Register: user created', { id: createdUser.id }); const safeUser = { id: createdUser.id, name: createdUser.name, email: createdUser.email, active: createdUser.active, created_at: createdUser.created_at }; return res.status(201).json({ message: 'Benutzer erfolgreich registriert', user: safeUser }); } catch (error) { if (error.name === 'SequelizeUniqueConstraintError') { return res.status(400).json({ message: 'Email-Adresse bereits in Verwendung' }); } console.error('Register error:', error); return res.status(500).json({ message: 'Ein Fehler ist aufgetreten', error: error.message }); } }; exports.login = async (req, res) => { const { email, password } = req.body; if (!email || !password) { return res.status(400).json({ message: 'Email und Passwort sind erforderlich' }); } try { const user = await User.findOne({ where: { email } }); if (!user) { return res.status(401).json({ message: 'Ungültige Anmeldedaten' }); } const validPassword = await bcrypt.compare(password, user.password); if (!validPassword) { return res.status(401).json({ message: 'Ungültige Anmeldedaten' }); } if (!user.active) { return res.status(403).json({ message: 'Benutzerkonto ist nicht aktiv' }); } const token = jwt.sign({ id: user.id, name: user.name, email: user.email }, 'zTxVgptmPl9!_dr%xxx9999(dd)', { expiresIn: '1h' }); return res.status(200).json({ message: 'Login erfolgreich', token, 'user': user }); } catch (error) { return res.status(500).json({ message: 'Ein Fehler ist aufgetreten' }); } }; exports.forgotPassword = async (req, res) => { const { email } = req.body; if (!email) { return res.status(400).json({ message: 'E-Mail-Adresse ist erforderlich' }); } try { const user = await User.findOne({ where: { email } }); if (!user) { // Aus Sicherheitsgründen immer Erfolg melden, auch wenn E-Mail nicht existiert return res.status(200).json({ message: 'Falls die E-Mail-Adresse in unserem System registriert ist, erhalten Sie einen Link zum Zurücksetzen des Passworts.' }); } // Alte Reset-Tokens für diesen User löschen await PasswordResetToken.destroy({ where: { userId: user.id } }); // Neuen Reset-Token generieren const token = crypto.randomBytes(32).toString('hex'); const expiresAt = new Date(Date.now() + 60 * 60 * 1000); // 1 Stunde await PasswordResetToken.create({ userId: user.id, token, expiresAt }); // Reset-URL generieren const resetUrl = `${process.env.FRONTEND_URL || 'http://localhost:8080'}/reset-password?token=${token}`; // E-Mail versenden const emailTemplate = getPasswordResetEmailTemplate(resetUrl, user.name); const mailOptions = { from: process.env.SMTP_FROM || 'noreply@miriamgemeinde.de', to: email, subject: emailTemplate.subject, html: emailTemplate.html, text: emailTemplate.text }; console.log('=== EMAIL SENDING DEBUG ==='); console.log('From:', mailOptions.from); console.log('To:', mailOptions.to); console.log('Subject:', mailOptions.subject); console.log('Reset URL:', resetUrl); console.log('==========================='); await transporter.sendMail(mailOptions); console.log('Password reset email sent to:', email); return res.status(200).json({ message: 'Falls die E-Mail-Adresse in unserem System registriert ist, erhalten Sie einen Link zum Zurücksetzen des Passworts.' }); } catch (error) { console.error('Forgot password error:', error); return res.status(500).json({ message: 'Ein Fehler ist aufgetreten' }); } }; exports.resetPassword = async (req, res) => { const { token, password } = req.body; if (!token || !password) { return res.status(400).json({ message: 'Token und neues Passwort sind erforderlich' }); } if (password.length < 6) { return res.status(400).json({ message: 'Passwort muss mindestens 6 Zeichen lang sein' }); } try { // Token validieren const resetToken = await PasswordResetToken.findOne({ where: { token, used: false, expiresAt: { [require('sequelize').Op.gt]: new Date() } }, include: [{ model: User, as: 'user' }] }); if (!resetToken) { return res.status(400).json({ message: 'Ungültiger oder abgelaufener Token' }); } // Passwort hashen und aktualisieren const hashedPassword = await bcrypt.hash(password, 10); await User.update( { password: hashedPassword }, { where: { id: resetToken.userId } } ); // Token als verwendet markieren await resetToken.update({ used: true }); console.log('Password reset successful for user:', resetToken.userId); return res.status(200).json({ message: 'Passwort erfolgreich zurückgesetzt' }); } catch (error) { console.error('Reset password error:', error); return res.status(500).json({ message: 'Ein Fehler ist aufgetreten' }); } }; exports.logout = async (req, res) => { const authHeader = req.header('Authorization'); if (!authHeader) { return res.status(400).json({ message: 'Kein Token bereitgestellt' }); } const token = authHeader.replace('Bearer ', ''); try { addTokenToBlacklist(token); return res.status(200).json({ message: 'Logout erfolgreich' }); } catch (error) { console.log(error); return res.status(500).json({ message: 'Ein Fehler ist beim Logout aufgetreten' }); } };