From 4fe6b27b8fe9f88e9e453ff9dd688e9c3c1a44f1 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Fri, 17 Oct 2025 23:13:33 +0200 Subject: [PATCH] Add password change routes to backend and frontend; update routing and UI components for password management --- backend/set-password.js | 71 ++++++ backend/src/controllers/PasswordController.js | 47 ++++ backend/src/index.js | 4 + backend/src/routes/password.js | 13 ++ backend/src/services/PasswordService.js | 68 ++++++ frontend/src/App.vue | 1 + frontend/src/router/index.js | 7 + frontend/src/views/PasswordChange.vue | 219 ++++++++++++++++++ 8 files changed, 430 insertions(+) create mode 100644 backend/set-password.js create mode 100644 backend/src/controllers/PasswordController.js create mode 100644 backend/src/routes/password.js create mode 100644 backend/src/services/PasswordService.js create mode 100644 frontend/src/views/PasswordChange.vue diff --git a/backend/set-password.js b/backend/set-password.js new file mode 100644 index 0000000..360b9be --- /dev/null +++ b/backend/set-password.js @@ -0,0 +1,71 @@ +/** + * Script zum Setzen eines neuen Passworts für einen User + * Verwendung: node set-password.js + */ + +require('dotenv').config(); +const bcrypt = require('bcrypt'); +const database = require('./src/config/database'); + +async function setPassword(userId, newPassword) { + try { + // Datenbank initialisieren + await database.initialize(); + + const { User, AuthInfo } = database.getModels(); + + // Hole User + const user = await User.findByPk(userId, { + include: [ + { + model: AuthInfo, + as: 'authInfo', + required: true + } + ] + }); + + if (!user || !user.authInfo) { + console.error(`❌ User ${userId} oder AuthInfo nicht gefunden`); + process.exit(1); + } + + // Hash Passwort + console.log('🔐 Hashe Passwort...'); + const hashedPassword = await bcrypt.hash(newPassword, 10); + + // Aktualisiere Passwort + user.authInfo.password_hash = hashedPassword; + await user.authInfo.save(); + + console.log(`✅ Passwort für User ${userId} (${user.full_name}) erfolgreich gesetzt!`); + console.log(` Email: ${user.authInfo.email}`); + + // Schließe Datenbankverbindung + await database.close(); + process.exit(0); + } catch (error) { + console.error('❌ Fehler beim Setzen des Passworts:', error.message); + await database.close(); + process.exit(1); + } +} + +// Argumente prüfen +const userId = parseInt(process.argv[2]); +const newPassword = process.argv[3]; + +if (!userId || !newPassword) { + console.log('Verwendung: node set-password.js '); + console.log('Beispiel: node set-password.js 1 MeinNeuesPasswort123'); + process.exit(1); +} + +if (newPassword.length < 6) { + console.error('❌ Passwort muss mindestens 6 Zeichen lang sein'); + process.exit(1); +} + +// Passwort setzen +setPassword(userId, newPassword); + diff --git a/backend/src/controllers/PasswordController.js b/backend/src/controllers/PasswordController.js new file mode 100644 index 0000000..adb7e88 --- /dev/null +++ b/backend/src/controllers/PasswordController.js @@ -0,0 +1,47 @@ +const PasswordService = require('../services/PasswordService'); + +/** + * Controller für Passwort-Verwaltung + * Verarbeitet HTTP-Requests und delegiert an PasswordService + */ +class PasswordController { + /** + * Ändert das Passwort + */ + async changePassword(req, res) { + try { + const userId = req.user?.id || 1; + const { oldPassword, newPassword, confirmPassword } = req.body; + + if (!oldPassword || !newPassword || !confirmPassword) { + return res.status(400).json({ + message: 'Alle Felder sind erforderlich' + }); + } + + if (newPassword !== confirmPassword) { + return res.status(400).json({ + message: 'Die Passwörter stimmen nicht überein' + }); + } + + await PasswordService.changePassword(userId, oldPassword, newPassword); + + res.json({ message: 'Passwort erfolgreich geändert' }); + } catch (error) { + console.error('Fehler beim Ändern des Passworts:', error); + + // Spezifische Fehlermeldungen + if (error.message.includes('alte Passwort')) { + return res.status(401).json({ message: error.message }); + } + + res.status(500).json({ + message: error.message || 'Fehler beim Ändern des Passworts' + }); + } + } +} + +module.exports = new PasswordController(); + diff --git a/backend/src/index.js b/backend/src/index.js index 61c75b2..6af350f 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -94,6 +94,10 @@ app.use('/api/holidays', authenticateToken, holidaysRouter); const profileRouter = require('./routes/profile'); app.use('/api/profile', authenticateToken, profileRouter); +// Password routes (geschützt) - MIT ID-Hashing +const passwordRouter = require('./routes/password'); +app.use('/api/password', authenticateToken, passwordRouter); + // Error handling middleware app.use((err, req, res, next) => { console.error(err.stack); diff --git a/backend/src/routes/password.js b/backend/src/routes/password.js new file mode 100644 index 0000000..bf2a40c --- /dev/null +++ b/backend/src/routes/password.js @@ -0,0 +1,13 @@ +const express = require('express'); +const router = express.Router(); +const PasswordController = require('../controllers/PasswordController'); + +/** + * Routen für Passwort-Verwaltung + */ + +// PUT /api/password - Passwort ändern +router.put('/', PasswordController.changePassword.bind(PasswordController)); + +module.exports = router; + diff --git a/backend/src/services/PasswordService.js b/backend/src/services/PasswordService.js new file mode 100644 index 0000000..1eb68e0 --- /dev/null +++ b/backend/src/services/PasswordService.js @@ -0,0 +1,68 @@ +const database = require('../config/database'); +const bcrypt = require('bcrypt'); + +/** + * Service-Klasse für Passwort-Verwaltung + */ +class PasswordService { + /** + * Ändert das Passwort eines Benutzers + * @param {number} userId - Benutzer-ID + * @param {string} oldPassword - Altes Passwort + * @param {string} newPassword - Neues Passwort + * @returns {Promise} + */ + async changePassword(userId, oldPassword, newPassword) { + const { User, AuthInfo } = database.getModels(); + + // Hole User und AuthInfo + const user = await User.findByPk(userId, { + include: [ + { + model: AuthInfo, + as: 'authInfo', + required: true + } + ] + }); + + if (!user || !user.authInfo) { + throw new Error('Benutzer oder Login-Informationen nicht gefunden'); + } + + const authInfo = user.authInfo; + + // Prüfe ob Passwort gesetzt ist + if (!authInfo.password_hash) { + throw new Error('Für diesen Account ist kein Passwort gesetzt. Möglicherweise verwenden Sie OAuth-Login.'); + } + + // Prüfe altes Passwort + const isValidPassword = await bcrypt.compare(oldPassword, authInfo.password_hash); + + if (!isValidPassword) { + throw new Error('Das alte Passwort ist nicht korrekt'); + } + + // Prüfe neues Passwort + if (!newPassword || newPassword.length < 6) { + throw new Error('Das neue Passwort muss mindestens 6 Zeichen lang sein'); + } + + if (newPassword === oldPassword) { + throw new Error('Das neue Passwort darf nicht mit dem alten übereinstimmen'); + } + + // Hash neues Passwort + const hashedPassword = await bcrypt.hash(newPassword, 10); + + // Aktualisiere Passwort + authInfo.password_hash = hashedPassword; + await authInfo.save(); + + console.log(`Passwort für User ${userId} erfolgreich geändert`); + } +} + +module.exports = new PasswordService(); + diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 2fd521d..44abd7f 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -74,6 +74,7 @@ const pageTitle = computed(() => { 'calendar': 'Kalender', 'admin-holidays': 'Feiertage', 'settings-profile': 'Persönliches', + 'settings-password': 'Passwort ändern', 'entries': 'Einträge', 'stats': 'Statistiken' } diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 1281664..88a9ece 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -17,6 +17,7 @@ import Workdays from '../views/Workdays.vue' import Calendar from '../views/Calendar.vue' import Holidays from '../views/Holidays.vue' import Profile from '../views/Profile.vue' +import PasswordChange from '../views/PasswordChange.vue' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), @@ -105,6 +106,12 @@ const router = createRouter({ component: Profile, meta: { requiresAuth: true } }, + { + path: '/settings/password', + name: 'settings-password', + component: PasswordChange, + meta: { requiresAuth: true } + }, { path: '/entries', name: 'entries', diff --git a/frontend/src/views/PasswordChange.vue b/frontend/src/views/PasswordChange.vue new file mode 100644 index 0000000..ae48341 --- /dev/null +++ b/frontend/src/views/PasswordChange.vue @@ -0,0 +1,219 @@ + + + + + +