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'); class AuthService { /** * User registrieren */ async register(userData) { const { name, email, password } = userData; if (!name || !email || !password) { throw new Error('VALIDATION_ERROR: Alle Felder sind erforderlich'); } // Prüfen ob E-Mail bereits existiert const existingUser = await User.findOne({ where: { email } }); if (existingUser) { throw new Error('EMAIL_ALREADY_EXISTS'); } const hashedPassword = await bcrypt.hash(password, 10); const user = await User.create({ name, email, password: hashedPassword, active: true }); return this.getSafeUserData(user); } /** * User einloggen */ async login(credentials) { const { email, password } = credentials; if (!email || !password) { throw new Error('VALIDATION_ERROR: Email und Passwort sind erforderlich'); } const user = await User.findOne({ where: { email } }); if (!user) { throw new Error('INVALID_CREDENTIALS'); } const validPassword = await bcrypt.compare(password, user.password); if (!validPassword) { throw new Error('INVALID_CREDENTIALS'); } if (!user.active) { throw new Error('ACCOUNT_INACTIVE'); } const token = jwt.sign( { id: user.id, name: user.name, email: user.email }, 'zTxVgptmPl9!_dr%xxx9999(dd)', { expiresIn: '1h' } ); return { message: 'Login erfolgreich', token, user: this.getSafeUserData(user) }; } /** * User ausloggen */ async logout(token) { if (!token) { throw new Error('VALIDATION_ERROR: Kein Token bereitgestellt'); } addTokenToBlacklist(token); return { message: 'Logout erfolgreich' }; } /** * Passwort vergessen - E-Mail senden */ async forgotPassword(email) { if (!email) { throw new Error('VALIDATION_ERROR: E-Mail-Adresse ist erforderlich'); } const user = await User.findOne({ where: { email } }); if (!user) { // Aus Sicherheitsgründen immer Erfolg melden return { 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); await transporter.sendMail({ from: process.env.SMTP_FROM || 'noreply@miriamgemeinde.de', to: email, subject: emailTemplate.subject, html: emailTemplate.html, text: emailTemplate.text }); console.log('Password reset email sent to:', email); return { message: 'Falls die E-Mail-Adresse in unserem System registriert ist, erhalten Sie einen Link zum Zurücksetzen des Passworts.' }; } /** * Passwort zurücksetzen */ async resetPassword(token, newPassword) { if (!token || !newPassword) { throw new Error('VALIDATION_ERROR: Token und neues Passwort sind erforderlich'); } if (newPassword.length < 6) { throw new Error('VALIDATION_ERROR: Passwort muss mindestens 6 Zeichen lang sein'); } // 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) { throw new Error('INVALID_RESET_TOKEN'); } // Passwort hashen und aktualisieren const hashedPassword = await bcrypt.hash(newPassword, 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 { message: 'Passwort erfolgreich zurückgesetzt' }; } /** * Sichere User-Daten extrahieren (ohne Passwort) */ getSafeUserData(user) { return { id: user.id, name: user.name, email: user.email, active: user.active, created_at: user.created_at }; } } module.exports = new AuthService();