feat: Implement account deletion feature with UI and API integration
Some checks failed
Deploy tt-tagebuch / deploy (push) Has been cancelled

This commit is contained in:
Torsten Schulz (local)
2026-05-18 14:12:14 +02:00
parent b9bbd45ae9
commit ecfd3bf851
13 changed files with 265 additions and 9 deletions

View File

@@ -1,4 +1,4 @@
import { register, activateUser, login, logout, requestPasswordReset, resetPassword } from '../services/authService.js';
import { register, activateUser, login, logout, deleteOwnAccount, requestPasswordReset, resetPassword } from '../services/authService.js';
const registerUser = async (req, res, next) => {
try {
@@ -45,6 +45,16 @@ const logoutUser = async (req, res, next) => {
}
};
const deleteAccount = async (req, res, next) => {
try {
const { password } = req.body || {};
const result = await deleteOwnAccount(req.user?.id, password);
res.status(200).json(result);
} catch (error) {
next(error);
}
};
const forgotPassword = async (req, res, next) => {
try {
const { email } = req.body;
@@ -65,4 +75,4 @@ const resetUserPassword = async (req, res, next) => {
}
};
export { registerUser, activate, loginUser, logoutUser, forgotPassword, resetUserPassword };
export { registerUser, activate, loginUser, logoutUser, deleteAccount, forgotPassword, resetUserPassword };

View File

@@ -1,5 +1,6 @@
import express from 'express';
import { registerUser, activate, loginUser, logoutUser, forgotPassword, resetUserPassword } from '../controllers/authController.js';
import { registerUser, activate, loginUser, logoutUser, deleteAccount, forgotPassword, resetUserPassword } from '../controllers/authController.js';
import { authenticate } from '../middleware/authMiddleware.js';
const router = express.Router();
@@ -7,6 +8,7 @@ router.post('/register', registerUser);
router.get('/activate/:activationCode', activate);
router.post('/login', loginUser);
router.post('/logout', logoutUser);
router.delete('/account', authenticate, deleteAccount);
router.post('/forgot-password', forgotPassword);
router.post('/reset-password', resetUserPassword);

View File

@@ -149,6 +149,11 @@ const SEO_ROUTE_CONFIG = {
description: 'Datenschutzerklärung von Trainingstagebuch.',
robots: 'index,follow',
},
'/konto-loeschen': {
title: 'Konto und Daten löschen | Trainingstagebuch',
description: 'Informationen zur Löschung des Benutzerkontos und personenbezogener Daten im Trainingstagebuch.',
robots: 'index,follow',
},
};
const SEO_NOINDEX_PREFIXES = [

View File

@@ -1,7 +1,18 @@
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import sequelize from '../database.js';
import User from '../models/User.js';
import UserToken from '../models/UserToken.js';
import UserClub from '../models/UserClub.js';
import Log from '../models/Log.js';
import ApiLog from '../models/ApiLog.js';
import MyTischtennis from '../models/MyTischtennis.js';
import MyTischtennisUpdateHistory from '../models/MyTischtennisUpdateHistory.js';
import MyTischtennisFetchLog from '../models/MyTischtennisFetchLog.js';
import ClickTtAccount from '../models/ClickTtAccount.js';
import BillingUserSetting from '../models/BillingUserSetting.js';
import BillingTemplate from '../models/BillingTemplate.js';
import BillingRun from '../models/BillingRun.js';
import crypto from 'crypto';
import { sendActivationEmail, sendPasswordResetEmail } from './emailService.js';
@@ -106,6 +117,47 @@ const logout = async (token) => {
return { message: 'Logout erfolgreich' };
};
const deleteOwnAccount = async (userId, password) => {
if (!userId || !password) {
const err = new Error('Passwort ist erforderlich');
err.status = 400;
throw err;
}
const user = await User.findByPk(userId);
const validPassword = user && await bcrypt.compare(password, user.password);
if (!validPassword) {
const err = new Error('Ungültiges Passwort');
err.status = 401;
throw err;
}
await sequelize.transaction(async (transaction) => {
await UserToken.destroy({ where: { userId }, transaction });
await UserClub.destroy({ where: { userId }, transaction });
await Log.destroy({ where: { userId }, transaction });
await ApiLog.update({ userId: null }, { where: { userId }, transaction });
await MyTischtennis.destroy({ where: { userId }, transaction });
await MyTischtennisUpdateHistory.destroy({ where: { userId }, transaction });
await MyTischtennisFetchLog.destroy({ where: { userId }, transaction });
await ClickTtAccount.destroy({ where: { userId }, transaction });
await BillingUserSetting.destroy({ where: { userId }, transaction });
await BillingTemplate.update({ createdByUserId: null }, { where: { createdByUserId: userId }, transaction });
await BillingRun.update(
{
createdByUserId: null,
selfRecipientName: 'Gelöschter Benutzer',
iban: null,
},
{ where: { selfRecipientUserId: userId }, transaction },
);
await BillingRun.update({ createdByUserId: null }, { where: { createdByUserId: userId }, transaction });
await user.destroy({ transaction });
});
return { success: true };
};
const requestPasswordReset = async (email) => {
if (!email) {
const err = new Error('E-Mail-Adresse ist erforderlich.');
@@ -182,4 +234,4 @@ const resetPassword = async (token, newPassword) => {
return { message: 'Passwort wurde erfolgreich geändert.' };
};
export { register, activateUser, login, logout, requestPasswordReset, resetPassword };
export { register, activateUser, login, logout, deleteOwnAccount, requestPasswordReset, resetPassword };