187 lines
4.9 KiB
JavaScript
187 lines
4.9 KiB
JavaScript
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();
|