Added Password Self-Service
This commit is contained in:
+23
-1
@@ -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");
|
||||
|
||||
+30
-10
@@ -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 = '<tr><td colspan="4" class="text-center text-gray-500 py-4">Noch keine Einträge.</td></tr>'; 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 += `
|
||||
<tr class="hover:bg-darkcard/50 transition-colors border-b border-darkborder/50">
|
||||
<td class="px-4 py-3 text-gray-400 whitespace-nowrap text-xs">${timeStr}</td>
|
||||
<td class="px-4 py-3"><span class="${isSystem ? 'text-gray-500' : 'text-blue-400 font-bold'}">${log.username}</span></td>
|
||||
<td class="px-4 py-3 font-bold text-white">${log.action}</td>
|
||||
<td class="px-4 py-3 text-gray-300 text-xs">${log.target || '-'}</td>
|
||||
</tr>`;
|
||||
tbody.innerHTML += `<tr class="hover:bg-darkcard/50 transition-colors border-b border-darkborder/50"><td class="px-4 py-3 text-gray-400 whitespace-nowrap text-xs">${timeStr}</td><td class="px-4 py-3"><span class="${isSystem ? 'text-gray-500' : 'text-blue-400 font-bold'}">${log.username}</span></td><td class="px-4 py-3 font-bold text-white">${log.action}</td><td class="px-4 py-3 text-gray-300 text-xs">${log.target || '-'}</td></tr>`;
|
||||
});
|
||||
}
|
||||
} catch(e) { tbody.innerHTML = '<tr><td colspan="4" class="text-center text-red-500 py-4">Fehler beim Laden.</td></tr>'; }
|
||||
|
||||
@@ -95,3 +95,29 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- NEU: PASSWORT ÄNDERN MODAL -->
|
||||
<div id="passwordModal" class="fixed inset-0 bg-black/80 hidden z-[100] flex items-center justify-center backdrop-blur-sm">
|
||||
<div class="bg-darkcard border border-darkborder rounded-xl shadow-2xl w-full max-w-sm overflow-hidden flex flex-col">
|
||||
<div class="p-5 border-b border-darkborder flex justify-between items-center bg-darkbg">
|
||||
<h2 class="text-lg font-bold text-white flex items-center gap-2">🔑 Passwort ändern</h2>
|
||||
<button onclick="closePasswordModal()" class="text-gray-400 hover:text-white transition-colors text-xl">✕</button>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<form id="changePasswordForm" class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-500 mb-1">Aktuelles Passwort</label>
|
||||
<input type="password" id="oldPassword" class="w-full bg-darkbg border border-darkborder rounded p-2 text-white text-sm focus:border-proxmox" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-500 mb-1">Neues Passwort</label>
|
||||
<input type="password" id="newPassword" class="w-full bg-darkbg border border-darkborder rounded p-2 text-white text-sm focus:border-proxmox" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-500 mb-1">Neues Passwort bestätigen</label>
|
||||
<input type="password" id="newPasswordConfirm" class="w-full bg-darkbg border border-darkborder rounded p-2 text-white text-sm focus:border-proxmox" required>
|
||||
</div>
|
||||
<button type="submit" class="w-full bg-proxmox hover:bg-orange-600 text-white font-bold py-2.5 rounded mt-2 transition-colors">Passwort aktualisieren</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user