From 8643f453702c8a85fc32315e105156c5c027b9e9 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 23 Aug 2026 19:36:19 +0200 Subject: [PATCH] Added Password Self-Service --- src/api.php | 24 +++++++++++++++++++++++- src/app.js | 40 ++++++++++++++++++++++++++++++---------- src/modals.php | 26 ++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 11 deletions(-) diff --git a/src/api.php b/src/api.php index 8dfe238..e9aba79 100644 --- a/src/api.php +++ b/src/api.php @@ -33,6 +33,29 @@ if ($action === 'logout') { if (!isset($_SESSION['user_id'])) { echo json_encode(['success' => false, 'error' => 'Zugriff verweigert.']); exit; } +// === NEU: PASSWORT ÄNDERN === +if ($action === 'change_password') { + $oldPass = $_POST['old_password'] ?? ''; + $newPass = $_POST['new_password'] ?? ''; + $userId = $_SESSION['user_id']; + + $stmt = $pdo->prepare("SELECT password_hash FROM users WHERE id = ?"); + $stmt->execute([$userId]); + $user = $stmt->fetch(PDO::FETCH_ASSOC); + + if ($user && password_verify($oldPass, $user['password_hash'])) { + if(strlen($newPass) < 4) { echo json_encode(['success' => false, 'error' => 'Passwort muss mindestens 4 Zeichen lang sein.']); exit; } + $newHash = password_hash($newPass, PASSWORD_DEFAULT); + $uStmt = $pdo->prepare("UPDATE users SET password_hash = ? WHERE id = ?"); + $uStmt->execute([$newHash, $userId]); + logAudit('Passwort geändert', 'Self-Service'); + echo json_encode(['success' => true]); + } else { + echo json_encode(['success' => false, 'error' => 'Das alte Passwort ist falsch.']); + } + exit; +} + $isAdmin = ($_SESSION['role'] ?? '') === 'admin'; session_write_close(); @@ -62,7 +85,6 @@ function checkVmPermission($pdo, $vmid) { $stmt = $pdo->prepare("SELECT permissions FROM users WHERE id = ?"); $stmt->execute([$userId]); return in_array($vmid, json_decode($stmt->fetchColumn(), true)['allowed_vms'] ?? []); } -// === AUDIT LOG ENDPUNKT === if ($action === 'get_audit_logs') { if (!$isAdmin) exit; $stmt = $pdo->query("SELECT * FROM audit_logs ORDER BY id DESC LIMIT 200"); diff --git a/src/app.js b/src/app.js index b4982fd..802631b 100644 --- a/src/app.js +++ b/src/app.js @@ -24,6 +24,34 @@ if (window.APP.isLoggedIn && window.APP.nodeCount > 0) { } } + // === NEU: PASSWORT ÄNDERN LOGIK === + const pwdModal = document.getElementById('passwordModal'); + window.openPasswordModal = function() { if(pwdModal) { pwdModal.classList.remove('hidden'); document.getElementById('changePasswordForm').reset(); } } + window.closePasswordModal = function() { if(pwdModal) pwdModal.classList.add('hidden'); } + + const pwdForm = document.getElementById('changePasswordForm'); + if(pwdForm) { + pwdForm.addEventListener('submit', async function(e) { + e.preventDefault(); + const oldP = document.getElementById('oldPassword').value; + const newP = document.getElementById('newPassword').value; + const confirmP = document.getElementById('newPasswordConfirm').value; + + if (newP !== confirmP) { alert('Die neuen Passwörter stimmen nicht überein!'); return; } + + const btn = this.querySelector('button[type="submit"]'); const oTxt = btn.innerText; btn.innerText = 'Speichere...'; + const fd = new FormData(); fd.append('old_password', oldP); fd.append('new_password', newP); + + try { + const res = await (await fetch('api.php?action=change_password', {method: 'POST', body: fd})).json(); + if(res.success) { + alert('Passwort erfolgreich geändert! Bitte neu anmelden.'); + closePasswordModal(); logout(); + } else { alert(res.error || 'Fehler beim Ändern des Passworts.'); } + } catch(err) { alert('Netzwerkfehler.'); } finally { btn.innerText = oTxt; } + }); + } + window.switchTab = function(tab) { ['pve', 'pbs', 'pmg'].forEach(t => { const el = document.getElementById('tab-' + t); const nav = document.getElementById('nav-tab-' + t); if (t === tab) { el.classList.remove('hidden'); setTimeout(() => el.classList.remove('opacity-0'), 50); nav.classList.add('tab-active' + (t === 'pve' ? '' : '-' + t)); } else { el.classList.add('hidden', 'opacity-0'); nav.classList.remove('tab-active', 'tab-active-pbs', 'tab-active-pmg'); } }); if(tab === 'pbs') fetchPbsStats(); if(tab === 'pmg') fetchPmgStats(); @@ -537,7 +565,7 @@ if (window.APP.isLoggedIn && window.APP.nodeCount > 0) { window.toggleCronJob = async function(id, newState) { const fd = new FormData(); fd.append('id', id); fd.append('is_active', newState); await fetch('api.php?action=toggle_cron_job', {method: 'POST', body: fd}); loadCronJobs(); } window.deleteCronJob = async function(id) { if(!confirm('Diesen geplanten Job wirklich löschen?')) return; const fd = new FormData(); fd.append('id', id); await fetch('api.php?action=delete_cron_job', {method: 'POST', body: fd}); loadCronJobs(); } - // === NEU: AUDIT LOG === + // === AUDIT LOG === const auditModal = document.getElementById('auditLogModal'); window.openAuditLog = async function() { auditModal.classList.remove('hidden'); @@ -549,18 +577,10 @@ if (window.APP.isLoggedIn && window.APP.nodeCount > 0) { tbody.innerHTML = ''; if(res.data.length === 0) { tbody.innerHTML = 'Noch keine Einträge.'; return; } res.data.forEach(log => { - // Konvertiere SQLite DATETIME in lokales deutsches Format const d = new Date(log.timestamp + 'Z'); const timeStr = d.toLocaleDateString('de-DE') + ' ' + d.toLocaleTimeString('de-DE'); const isSystem = log.username === 'System'; - - tbody.innerHTML += ` - - ${timeStr} - ${log.username} - ${log.action} - ${log.target || '-'} - `; + tbody.innerHTML += `${timeStr}${log.username}${log.action}${log.target || '-'}`; }); } } catch(e) { tbody.innerHTML = 'Fehler beim Laden.'; } diff --git a/src/modals.php b/src/modals.php index 8eb983c..d6976fe 100644 --- a/src/modals.php +++ b/src/modals.php @@ -94,4 +94,30 @@ + + + \ No newline at end of file