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) });
|
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)
|
// GET /v1/auth/me -> aktuell eingeloggter Nutzer (zur Session-Wiederherstellung)
|
||||||
authRouter.get('/auth/me', requireAuth, async (req: AuthedRequest, res: Response) => {
|
authRouter.get('/auth/me', requireAuth, async (req: AuthedRequest, res: Response) => {
|
||||||
const user = await prisma.user.findUnique({ where: { id: req.user!.id } });
|
const user = await prisma.user.findUnique({ where: { id: req.user!.id } });
|
||||||
|
|||||||
@ -590,6 +590,7 @@
|
|||||||
<div class="topbar">
|
<div class="topbar">
|
||||||
<button class="hamburger-btn" onclick="openSidebar()">☰</button>
|
<button class="hamburger-btn" onclick="openSidebar()">☰</button>
|
||||||
<span id="whoAmI"></span>
|
<span id="whoAmI"></span>
|
||||||
|
<button class="btn-secondary" onclick="openChangePassword()">Passwort ändern</button>
|
||||||
<button class="btn-secondary" onclick="logout()">Abmelden</button>
|
<button class="btn-secondary" onclick="logout()">Abmelden</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -1081,6 +1082,35 @@
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
<script>
|
||||||
const API_BASE_URL = window.WG_API_BASE_URL ||
|
const API_BASE_URL = window.WG_API_BASE_URL ||
|
||||||
(location.protocol === 'file:' ? 'http://localhost:3000/v1' : '/v1');
|
(location.protocol === 'file:' ? 'http://localhost:3000/v1' : '/v1');
|
||||||
@ -1363,6 +1393,7 @@
|
|||||||
if (isLandlord) initUtilityStatementForm();
|
if (isLandlord) initUtilityStatementForm();
|
||||||
if (isLandlord) initCraftsmanForm();
|
if (isLandlord) initCraftsmanForm();
|
||||||
initRatingForm();
|
initRatingForm();
|
||||||
|
initChangePasswordForm();
|
||||||
|
|
||||||
loadData();
|
loadData();
|
||||||
loadCleaningTasks();
|
loadCleaningTasks();
|
||||||
@ -2960,6 +2991,58 @@
|
|||||||
document.getElementById('docPreviewBody').innerHTML = '';
|
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) {
|
function buildProfileCompletenessBadge(user) {
|
||||||
const missing = [];
|
const missing = [];
|
||||||
if (!user.phoneNumber) missing.push('Telefonnummer');
|
if (!user.phoneNumber) missing.push('Telefonnummer');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user