From 19c6facf7171c04f29ae5b010f3b51b3a1b81ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reinhard=20X=2E=20F=C3=BCrst?= Date: Fri, 7 Aug 2026 08:03:27 +0000 Subject: [PATCH] =?UTF-8?q?espid2sensor:=20User-Liste=20mit=20Passwort-Res?= =?UTF-8?q?et=20und=20L=C3=B6schen=20im=20User-Tab?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ersetzt das manuelle E-Mail-Eingabefeld für den Passwort-Reset durch eine Liste aller angemeldeten User. Pro User stehen zwei Aktionen zur Verfügung: Passwort zurücksetzen (nutzt die bestehende /api/resetPassword-Route) und Löschen (neue DELETE /api/users/:id-Route, admin-only, verhindert Löschen des eigenen Accounts). Neue GET /api/users-Route liefert Email/Rolle aller User (admin-only). --- espid2sensor/public/global.js | 99 +++++++++++++++++++++++++---------- espid2sensor/routes/api.js | 25 +++++++++ espid2sensor/views/index.pug | 18 +++---- 3 files changed, 104 insertions(+), 38 deletions(-) diff --git a/espid2sensor/public/global.js b/espid2sensor/public/global.js index 096abf0..6a06aac 100644 --- a/espid2sensor/public/global.js +++ b/espid2sensor/public/global.js @@ -34,6 +34,7 @@ document.addEventListener('DOMContentLoaded', () => { const data = await res.json(); if (data.success) { userResult.textContent = 'User erfolgreich angelegt!'; + if (window.loadUsers) window.loadUsers(); } else { userResult.textContent = data.error || 'Fehler beim Anlegen.'; } @@ -77,34 +78,6 @@ document.addEventListener('DOMContentLoaded', () => { }); } - const resetPasswordBtn = document.getElementById('resetPasswordBtn'); - if (resetPasswordBtn) { - resetPasswordBtn.addEventListener('click', async () => { - const email = document.getElementById('resetEmail').value.trim(); - const newPassword = document.getElementById('resetNewPassword').value; - const resetPasswordResult = document.getElementById('resetPasswordResult'); - if (!email || !newPassword) { - resetPasswordResult.textContent = 'Email und neues Passwort erforderlich.'; - return; - } - try { - const res = await fetch('/api/resetPassword', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ email, newPassword }) - }); - const data = await res.json(); - if (data.success) { - resetPasswordResult.textContent = 'Passwort erfolgreich zurückgesetzt!'; - document.getElementById('resetPasswordForm').reset(); - } else { - resetPasswordResult.textContent = data.error || 'Fehler beim Zurücksetzen.'; - } - } catch (err) { - resetPasswordResult.textContent = 'Serverfehler.'; - } - }); - } }); function updateSortArrows() { @@ -467,6 +440,73 @@ function showModal(message, showCancelButton, callback) { }); } + async function loadUsers() { + const usersTableBody = document.querySelector('#usersTable tbody'); + const usersResult = document.getElementById('usersResult'); + if (!usersTableBody) return; + try { + const res = await fetch('/api/users'); + const users = await res.json(); + usersTableBody.innerHTML = ''; + users.forEach(u => { + const tr = document.createElement('tr'); + tr.innerHTML = ` + ${u.email} + ${u.role} + +
+ + +
+ + `; + usersTableBody.appendChild(tr); + }); + + document.querySelectorAll('.resetUserBtn').forEach(btn => { + btn.addEventListener('click', async () => { + const email = btn.getAttribute('data-email'); + const newPassword = prompt(`Neues Passwort für ${email}:`); + if (!newPassword) return; + try { + const res = await fetch('/api/resetPassword', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email, newPassword }) + }); + const data = await res.json(); + usersResult.textContent = data.success ? `Passwort für ${email} zurückgesetzt.` : (data.error || 'Fehler beim Zurücksetzen.'); + } catch (err) { + usersResult.textContent = 'Serverfehler.'; + } + }); + }); + + document.querySelectorAll('.deleteUserBtn').forEach(btn => { + btn.addEventListener('click', () => { + const id = btn.getAttribute('data-id'); + showModal('User wirklich löschen?', true, async (confirmed) => { + if (!confirmed) return; + try { + const res = await fetch(`/api/users/${id}`, { method: 'DELETE' }); + const data = await res.json(); + if (data.success) { + usersResult.textContent = 'User gelöscht.'; + await loadUsers(); + } else { + usersResult.textContent = data.error || 'Fehler beim Löschen.'; + } + } catch (err) { + usersResult.textContent = 'Serverfehler.'; + } + }); + }); + }); + } catch (err) { + console.error(err); + } + } + saveBtn.addEventListener('click', saveEntry); refreshBtn.addEventListener('click', loadEntries); cancelBtn.addEventListener('click', () => clearForm(true)); @@ -475,9 +515,10 @@ function showModal(message, showCancelButton, callback) { tabInput.addEventListener('click', () => showTab('input')) tabList.addEventListener('click', () => showTab('list')) const tabUser = document.getElementById('tabUser'); - if (tabUser) tabUser.addEventListener('click', () => showTab('user')) + if (tabUser) tabUser.addEventListener('click', () => { showTab('user'); loadUsers(); }) loadEntries(); + window.loadUsers = loadUsers; }); window.showTab = showTab; \ No newline at end of file diff --git a/espid2sensor/routes/api.js b/espid2sensor/routes/api.js index 2f2af8c..96f5bf8 100644 --- a/espid2sensor/routes/api.js +++ b/espid2sensor/routes/api.js @@ -127,4 +127,29 @@ export function registerApiRoutes(app, requireLogin) { res.status(500).json({ error: 'Fehler beim Zurücksetzen' }); } }); + + app.get('/api/users', requireLogin, async (req, res) => { + if (!req.session.isAdmin) return res.status(403).json({ error: 'Nur Admins erlaubt' }); + try { + const users = await usersCollection.find({}, { projection: { email: 1, role: 1 } }).sort({ email: 1 }).toArray(); + res.json(users); + } catch (err) { + res.status(500).json({ error: 'Fehler beim Laden der User' }); + } + }); + + app.delete('/api/users/:id', requireLogin, async (req, res) => { + if (!req.session.isAdmin) return res.status(403).json({ error: 'Nur Admins erlaubt' }); + const { id } = req.params; + if (id === String(req.session.userId)) { + return res.status(400).json({ error: 'Der eigene Account kann nicht gelöscht werden' }); + } + try { + const result = await usersCollection.deleteOne({ _id: new ObjectId(id) }); + if (result.deletedCount === 0) return res.status(404).json({ error: 'User nicht gefunden' }); + res.json({ success: true }); + } catch (err) { + res.status(500).json({ error: 'Fehler beim Löschen' }); + } + }); } diff --git a/espid2sensor/views/index.pug b/espid2sensor/views/index.pug index 4c76f61..b628807 100644 --- a/espid2sensor/views/index.pug +++ b/espid2sensor/views/index.pug @@ -103,15 +103,15 @@ html(lang="de") button#userCancelBtn(type="button") Abbrechen div#userResult div.card - h2 Passwort eines Users zurücksetzen - form#resetPasswordForm - label(for="resetEmail") Email: - input#resetEmail(type="email" required) - label(for="resetNewPassword") Neues Passwort: - input#resetNewPassword(type="password" required) - .twobuttons - button#resetPasswordBtn(type="button") Zurücksetzen - div#resetPasswordResult + h2 Angemeldete User + table#usersTable + thead + tr + th Email + th Rolle + th Aktionen + tbody + div#usersResult #version Version: #{version} vom #{vdate} script(type="module" src="/global.js") \ No newline at end of file