From 51e47cf9f9da2049ba25394d5208a2148d1c503f Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Fri, 21 Nov 2025 09:17:48 +0100 Subject: [PATCH] Refactor global error handling in server and improve logging in myTischtennisService This commit moves the global error handling middleware in server.js to ensure it is applied after all routes. It also enhances the error handling in myTischtennisService by adding detailed logging for login attempts and failures, improving the visibility of issues during the login process. Additionally, the decryptData function in encrypt.js is updated to check if data is already decrypted, enhancing its robustness. Frontend changes include a minor adjustment to the button type in MyTischtennisAccount.vue for better accessibility. --- backend/server.js | 76 ++++++++++----------- backend/services/myTischtennisService.js | 10 ++- backend/utils/encrypt.js | 17 ++++- frontend/src/views/MyTischtennisAccount.vue | 7 +- 4 files changed, 67 insertions(+), 43 deletions(-) diff --git a/backend/server.js b/backend/server.js index 7bb6b5e..1fde812 100644 --- a/backend/server.js +++ b/backend/server.js @@ -80,44 +80,6 @@ process.on('unhandledRejection', (reason, promise) => { console.error('[unhandledRejection]', reason); }); -// Globale Fehlerbehandlung für API-Routen -app.use((err, req, res, next) => { - if (res.headersSent) { - return next(err); - } - - const status = err?.statusCode || err?.status || 500; - - // Unterstützung für Fehlercodes - let errorResponse; - if (err instanceof HttpError && err.errorCode) { - // Neues Format mit Fehlercode - errorResponse = err.toJSON(); - } else { - // Legacy-Format: String-Nachricht - const message = err?.message || 'Interner Serverfehler'; - errorResponse = { - message - }; - } - - const response = { - success: false, - ...errorResponse, - // Für Rückwärtskompatibilität: error-Feld mit Nachricht - error: errorResponse.message || errorResponse.code || 'Interner Serverfehler' - }; - - if (process.env.NODE_ENV === 'dev' || process.env.NODE_ENV === 'development') { - response.debug = { - stack: err?.stack || null - }; - } - - console.error('[ExpressError]', err); - res.status(status).json(response); -}); - app.use('/api/auth', authRoutes); app.use('/api/clubs', clubRoutes); app.use('/api/clubmembers', memberRoutes); @@ -196,6 +158,44 @@ const setCanonicalTag = (req, res, next) => { app.use(setCanonicalTag); app.use(express.static(path.join(__dirname, '../frontend/dist'))); +// Globale Fehlerbehandlung für API-Routen (MUSS nach allen Routes sein!) +app.use((err, req, res, next) => { + if (res.headersSent) { + return next(err); + } + + const status = err?.statusCode || err?.status || 500; + + // Unterstützung für Fehlercodes + let errorResponse; + if (err instanceof HttpError && err.errorCode) { + // Neues Format mit Fehlercode + errorResponse = err.toJSON(); + } else { + // Legacy-Format: String-Nachricht + const message = err?.message || 'Interner Serverfehler'; + errorResponse = { + message + }; + } + + const response = { + success: false, + ...errorResponse, + // Für Rückwärtskompatibilität: error-Feld mit Nachricht + error: errorResponse.message || errorResponse.code || 'Interner Serverfehler' + }; + + if (process.env.NODE_ENV === 'dev' || process.env.NODE_ENV === 'development') { + response.debug = { + stack: err?.stack || null + }; + } + + console.error('[ExpressError]', err); + res.status(status).json(response); +}); + (async () => { try { await sequelize.authenticate(); diff --git a/backend/services/myTischtennisService.js b/backend/services/myTischtennisService.js index a6cafa7..17cb480 100644 --- a/backend/services/myTischtennisService.js +++ b/backend/services/myTischtennisService.js @@ -182,6 +182,10 @@ class MyTischtennisService { if (!password) { if (hasStoredPassword) { password = account.getPassword(); + if (!password) { + console.error('[myTischtennisService.verifyLogin] Could not decrypt stored password'); + throw new HttpError('Gespeichertes Passwort konnte nicht entschlüsselt werden. Bitte Passwort erneut eingeben.', 400); + } } else if (hasValidSession) { // Prüfe, ob bestehende Session noch gültig ist const profileResult = await myTischtennisClient.getUserProfile(account.cookie); @@ -214,7 +218,9 @@ class MyTischtennisService { } // Login-Versuch mit Passwort + console.log('[myTischtennisService.verifyLogin] Attempting login for user:', account.email); const loginResult = await myTischtennisClient.login(account.email, password); + console.log('[myTischtennisService.verifyLogin] Login result:', { success: loginResult.success, error: loginResult.error }); if (loginResult.success) { account.lastLoginSuccess = now; @@ -248,7 +254,9 @@ class MyTischtennisService { }; } else { await account.save(); // Save lastLoginAttempt - throw new HttpError(loginResult.error || 'myTischtennis-Login fehlgeschlagen', 401); + const errorMessage = loginResult.error || 'myTischtennis-Login fehlgeschlagen'; + console.error('[myTischtennisService.verifyLogin] Login failed:', errorMessage); + throw new HttpError(errorMessage, 401); } } diff --git a/backend/utils/encrypt.js b/backend/utils/encrypt.js index 03477b4..be6db89 100644 --- a/backend/utils/encrypt.js +++ b/backend/utils/encrypt.js @@ -26,14 +26,27 @@ function decryptData(data) { if (!data || data === null || data === undefined || data === '') { return null; } + + // Prüfe, ob die Daten bereits entschlüsselt sind (nicht im Hex-Format) + // Hex-Strings haben nur 0-9 und a-f Zeichen und gerade Länge + const isHexString = /^[0-9a-fA-F]+$/.test(data) && data.length % 2 === 0; + + if (!isHexString) { + // Daten sind wahrscheinlich bereits entschlüsselt oder in einem anderen Format + // Versuche, sie direkt zurückzugeben + return data; + } + try { const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(process.env.ENCRYPTION_KEY, 'hex'), Buffer.alloc(16, 0)); let decrypted = decipher.update(data, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } catch (error) { - console.error('[decryptData] Error decrypting data:', error); - return null; + // Wenn Entschlüsselung fehlschlägt, versuche die Daten direkt zurückzugeben + // (falls sie bereits entschlüsselt sind) + console.warn('[decryptData] Error decrypting data, returning as-is:', error.message); + return data; } } diff --git a/frontend/src/views/MyTischtennisAccount.vue b/frontend/src/views/MyTischtennisAccount.vue index 7f60a1c..147c4d0 100644 --- a/frontend/src/views/MyTischtennisAccount.vue +++ b/frontend/src/views/MyTischtennisAccount.vue @@ -46,7 +46,7 @@
- +
@@ -287,14 +287,17 @@ export default { }, async testConnection() { + console.log('[testConnection] Starting connection test...'); try { - await apiClient.post('/mytischtennis/verify'); + const response = await apiClient.post('/mytischtennis/verify'); + console.log('[testConnection] Response:', response); this.$store.dispatch('showMessage', { text: this.$t('myTischtennisAccount.loginSuccessful'), type: 'success' }); await this.loadAccount(); // Aktualisiere Account-Daten inkl. clubId, fedNickname } catch (error) { + console.error('[testConnection] Error:', error); const message = getSafeErrorMessage(error, 'Login fehlgeschlagen'); if (error.response?.status === 400 && message.includes('Kein Passwort gespeichert')) {