Passwort-ändern-Funktion für alle Rollen hinzufügen
Neuer Endpoint POST /v1/auth/change-password (verlangt aktuelles Passwort zur Bestätigung) sowie Modal + Button im Topbar für Vermieter und Mieter.
This commit is contained in:
parent
dd24d7217a
commit
78810002b4
@ -32,6 +32,32 @@ authRouter.post('/auth/login', async (req: Request, res: Response) => {
|
||||
res.status(200).json({ token, user: publicUser(user) });
|
||||
});
|
||||
|
||||
// POST /v1/auth/change-password { currentPassword, newPassword } -> { ok: true }
|
||||
// Für alle Rollen (Vermieter/Admin/Mieter/Handwerker) gleichermaßen nutzbar —
|
||||
// ändert ausschließlich das eigene Passwort, verlangt zur Bestätigung das
|
||||
// aktuelle Passwort.
|
||||
authRouter.post('/auth/change-password', requireAuth, async (req: AuthedRequest, res: Response) => {
|
||||
const { currentPassword, newPassword } = req.body || {};
|
||||
if (!currentPassword || !newPassword) {
|
||||
return res.status(400).json({ error: 'Aktuelles und neues Passwort erforderlich' });
|
||||
}
|
||||
if (String(newPassword).length < 8) {
|
||||
return res.status(400).json({ error: 'Das neue Passwort muss mindestens 8 Zeichen lang sein' });
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { id: req.user!.id } });
|
||||
if (!user) return res.status(404).json({ error: 'Nutzer nicht gefunden' });
|
||||
|
||||
const currentOk = await bcrypt.compare(String(currentPassword), user.passwordHash);
|
||||
if (!currentOk) {
|
||||
return res.status(401).json({ error: 'Aktuelles Passwort ist falsch' });
|
||||
}
|
||||
|
||||
const passwordHash = await bcrypt.hash(String(newPassword), 12);
|
||||
await prisma.user.update({ where: { id: user.id }, data: { passwordHash } });
|
||||
res.status(200).json({ ok: true });
|
||||
});
|
||||
|
||||
// GET /v1/auth/me -> aktuell eingeloggter Nutzer (zur Session-Wiederherstellung)
|
||||
authRouter.get('/auth/me', requireAuth, async (req: AuthedRequest, res: Response) => {
|
||||
const user = await prisma.user.findUnique({ where: { id: req.user!.id } });
|
||||
|
||||
@ -590,6 +590,7 @@
|
||||
<div class="topbar">
|
||||
<button class="hamburger-btn" onclick="openSidebar()">☰</button>
|
||||
<span id="whoAmI"></span>
|
||||
<button class="btn-secondary" onclick="openChangePassword()">Passwort ändern</button>
|
||||
<button class="btn-secondary" onclick="logout()">Abmelden</button>
|
||||
</div>
|
||||
|
||||
@ -1081,6 +1082,35 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================= PASSWORT ÄNDERN (Modal) ================= -->
|
||||
<div id="changePasswordOverlay" class="doc-preview-overlay" style="display:none" onclick="if (event.target === this) closeChangePassword()">
|
||||
<div class="doc-preview-box" style="max-width:380px;">
|
||||
<div class="doc-preview-header">
|
||||
<strong>Passwort ändern</strong>
|
||||
<button type="button" class="btn-secondary" onclick="closeChangePassword()">Schließen ✕</button>
|
||||
</div>
|
||||
<div style="padding:20px;">
|
||||
<div id="changePasswordError" style="display:none" class="form-error"></div>
|
||||
<div id="changePasswordSuccess" style="display:none" class="form-success"></div>
|
||||
<form id="changePasswordForm">
|
||||
<div class="field">
|
||||
<label for="currentPassword">Aktuelles Passwort</label>
|
||||
<input type="password" id="currentPassword" required autocomplete="current-password" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="newPassword">Neues Passwort (min. 8 Zeichen)</label>
|
||||
<input type="password" id="newPassword" required minlength="8" autocomplete="new-password" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="newPasswordRepeat">Neues Passwort wiederholen</label>
|
||||
<input type="password" id="newPasswordRepeat" required minlength="8" autocomplete="new-password" />
|
||||
</div>
|
||||
<button type="submit" class="btn-primary" id="changePasswordSubmitBtn">Passwort ändern</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const API_BASE_URL = window.WG_API_BASE_URL ||
|
||||
(location.protocol === 'file:' ? 'http://localhost:3000/v1' : '/v1');
|
||||
@ -1363,6 +1393,7 @@
|
||||
if (isLandlord) initUtilityStatementForm();
|
||||
if (isLandlord) initCraftsmanForm();
|
||||
initRatingForm();
|
||||
initChangePasswordForm();
|
||||
|
||||
loadData();
|
||||
loadCleaningTasks();
|
||||
@ -2960,6 +2991,58 @@
|
||||
document.getElementById('docPreviewBody').innerHTML = '';
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// PASSWORT ÄNDERN (für alle Rollen)
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
function openChangePassword() {
|
||||
document.getElementById('changePasswordError').style.display = 'none';
|
||||
document.getElementById('changePasswordSuccess').style.display = 'none';
|
||||
document.getElementById('changePasswordForm').reset();
|
||||
document.getElementById('changePasswordOverlay').style.display = 'flex';
|
||||
}
|
||||
|
||||
function closeChangePassword() {
|
||||
document.getElementById('changePasswordOverlay').style.display = 'none';
|
||||
}
|
||||
|
||||
function initChangePasswordForm() {
|
||||
const form = document.getElementById('changePasswordForm');
|
||||
const errorBox = document.getElementById('changePasswordError');
|
||||
const successBox = document.getElementById('changePasswordSuccess');
|
||||
form.onsubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
errorBox.style.display = 'none';
|
||||
successBox.style.display = 'none';
|
||||
const currentPassword = document.getElementById('currentPassword').value;
|
||||
const newPassword = document.getElementById('newPassword').value;
|
||||
const newPasswordRepeat = document.getElementById('newPasswordRepeat').value;
|
||||
if (newPassword !== newPasswordRepeat) {
|
||||
errorBox.textContent = 'Die neuen Passwörter stimmen nicht überein.';
|
||||
errorBox.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
const submitBtn = document.getElementById('changePasswordSubmitBtn');
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = 'Ändert…';
|
||||
try {
|
||||
await apiFetch('/auth/change-password', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ currentPassword, newPassword }),
|
||||
});
|
||||
form.reset();
|
||||
successBox.textContent = 'Passwort erfolgreich geändert.';
|
||||
successBox.style.display = 'block';
|
||||
} catch (err) {
|
||||
errorBox.textContent = err.message;
|
||||
errorBox.style.display = 'block';
|
||||
} finally {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = 'Passwort ändern';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function buildProfileCompletenessBadge(user) {
|
||||
const missing = [];
|
||||
if (!user.phoneNumber) missing.push('Telefonnummer');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user