Füge verbesserte Fehlerbehandlung und Wiederholungslogik zur Benutzerregistrierung im authController hinzu, aktualisiere die Router-Konfiguration für die Registrierungsseite und implementiere ein Dialogfeld zur Benutzerinteraktion in der RegisterContent-Komponente.

This commit is contained in:
Torsten Schulz (local)
2025-09-23 16:04:13 +02:00
parent 131f6bd753
commit 7c09abf534
3 changed files with 106 additions and 25 deletions

View File

@@ -3,6 +3,10 @@ const { User } = require('../models');
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const { addTokenToBlacklist } = require('../utils/blacklist'); const { addTokenToBlacklist } = require('../utils/blacklist');
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
exports.register = async (req, res) => { exports.register = async (req, res) => {
const { name, email, password } = req.body; const { name, email, password } = req.body;
if (!name || !email || !password) { if (!name || !email || !password) {
@@ -10,13 +14,52 @@ exports.register = async (req, res) => {
} }
try { try {
const hashedPassword = await bcrypt.hash(password, 10); const hashedPassword = await bcrypt.hash(password, 10);
const user = await User.create({ name, email, password: hashedPassword, active: true }); console.log('Register: creating user', { email });
res.status(201).json({ message: 'Benutzer erfolgreich registriert', user });
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: true });
} 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) { } catch (error) {
if (error.name === 'SequelizeUniqueConstraintError') { if (error.name === 'SequelizeUniqueConstraintError') {
return res.status(400).json({ message: 'Email-Adresse bereits in Verwendung' }); return res.status(400).json({ message: 'Email-Adresse bereits in Verwendung' });
} }
res.status(500).json({ message: 'Ein Fehler ist aufgetreten' }); console.error('Register error:', error);
return res.status(500).json({ message: 'Ein Fehler ist aufgetreten', error: error.message });
} }
}; };
@@ -38,9 +81,9 @@ exports.login = async (req, res) => {
return res.status(403).json({ message: 'Benutzerkonto ist nicht aktiv' }); 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' }); const token = jwt.sign({ id: user.id, name: user.name, email: user.email }, 'zTxVgptmPl9!_dr%xxx9999(dd)', { expiresIn: '1h' });
res.status(200).json({ message: 'Login erfolgreich', token, 'user': user }); return res.status(200).json({ message: 'Login erfolgreich', token, 'user': user });
} catch (error) { } catch (error) {
res.status(500).json({ message: 'Ein Fehler ist aufgetreten' }); return res.status(500).json({ message: 'Ein Fehler ist aufgetreten' });
} }
}; };
@@ -52,9 +95,9 @@ exports.logout = async (req, res) => {
const token = authHeader.replace('Bearer ', ''); const token = authHeader.replace('Bearer ', '');
try { try {
addTokenToBlacklist(token); addTokenToBlacklist(token);
res.status(200).json({ message: 'Logout erfolgreich' }); return res.status(200).json({ message: 'Logout erfolgreich' });
} catch (error) { } catch (error) {
console.log(error); console.log(error);
res.status(500).json({ message: 'Ein Fehler ist beim Logout aufgetreten' }); return res.status(500).json({ message: 'Ein Fehler ist beim Logout aufgetreten' });
} }
}; };

View File

@@ -19,10 +19,19 @@
<p> <p>
<router-link to="/forgot-password">Passwort vergessen?</router-link> <router-link to="/forgot-password">Passwort vergessen?</router-link>
</p> </p>
<div v-if="dialogVisible" class="dialog">
<div class="dialog-content">
<h3>{{ dialogTitle }}</h3>
<p>{{ dialogMessage }}</p>
<button type="button" @click="closeDialog">Schließen</button>
</div>
</div>
</div> </div>
</template> </template>
<script> <script>
import axios from '../../axios';
export default { export default {
name: 'RegisterComponent', name: 'RegisterComponent',
@@ -41,32 +50,27 @@ export default {
methods: { methods: {
async register() { async register() {
try { try {
const response = await fetch('/register', { const response = await axios.post('/auth/register', {
method: 'POST', name: this.name,
headers: { email: this.email,
'Content-Type': 'application/json' password: this.password
},
body: JSON.stringify({
name: this.name,
email: this.email,
password: this.password
})
}); });
if (response.ok) { this.showDialog('Registrierung erfolgreich', response.data?.message || 'Ihr Konto wurde erfolgreich erstellt.');
await response.json(); this.name = '';
this.showDialog('Registrierung erfolgreich', 'Ihr Konto wurde erfolgreich erstellt.'); this.email = '';
} else { this.password = '';
const error = await response.json();
this.showDialog('Fehler', error.message);
}
} catch (err) { } catch (err) {
this.showDialog('Ein Fehler ist aufgetreten', err.message); const message = err?.response?.data?.message || err?.message || 'Ein unbekannter Fehler ist aufgetreten';
this.showDialog('Fehler', message);
} }
}, },
showDialog(title, message) { showDialog(title, message) {
this.dialogTitle = title; this.dialogTitle = title;
this.dialogMessage = message; this.dialogMessage = message;
this.dialogVisible = true; this.dialogVisible = true;
},
closeDialog() {
this.dialogVisible = false;
} }
} }
}; };
@@ -87,4 +91,22 @@ label {
button { button {
margin-top: 20px; margin-top: 20px;
} }
.dialog {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.4);
}
.dialog-content {
background: #fff;
padding: 16px;
border-radius: 4px;
max-width: 420px;
width: 90%;
}
</style> </style>

View File

@@ -51,6 +51,7 @@ router.beforeEach(async (to, from, next) => {
const routes = generateRoutesFromMenu(store.state.menuData); const routes = generateRoutesFromMenu(store.state.menuData);
routes.forEach(route => router.addRoute(route)); routes.forEach(route => router.addRoute(route));
addEditPagesRoute(); addEditPagesRoute();
addRegisterRoute();
router.addRoute({ router.addRoute({
path: '/:pathMatch(.*)*', path: '/:pathMatch(.*)*',
components: { components: {
@@ -83,6 +84,21 @@ function addEditPagesRoute() {
}); });
} }
function addRegisterRoute() {
if (router.hasRoute('/register')) {
router.removeRoute('/register');
}
router.addRoute({
path: '/register',
components: {
default: () => import('./content/authentication/RegisterContent.vue'),
rightColumn: loadComponent('ImageContent')
},
name: 'register'
});
}
addEditPagesRoute(); addEditPagesRoute();
addRegisterRoute();
export default router; export default router;