From 3a4fa581c0c0b0b96926d37cd5a6cb25a07e6cd6 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 03:44:10 +0000 Subject: [PATCH] Portal: Mitarbeiter-Login gesperrt (Hinweis aufs Ticketsystem), alte Mitarbeiter-Sitzungen enden Co-Authored-By: Claude Opus 5.5 --- public/app.js | 89 +++-------------- public/login.css | 21 ++-- public/login.html | 27 +----- server/routes/auth.js | 156 +++--------------------------- server/services/appwriteClient.js | 21 +--- server/services/staffMfa.js | 80 --------------- test/kundenansichtEnde.test.js | 11 +-- test/kundenansichtGuard.test.js | 2 +- test/staffLogin.test.js | 134 ++++++++++++------------- test/staffMfa.test.js | 89 ----------------- test/staffSwitch.test.js | 33 ------- test/support/appwriteMock.js | 10 ++ test/ui/kundenansicht.ui.mjs | 31 ++++-- 13 files changed, 145 insertions(+), 559 deletions(-) delete mode 100644 server/services/staffMfa.js delete mode 100644 test/staffMfa.test.js diff --git a/public/app.js b/public/app.js index 4ad24ce..913760d 100644 --- a/public/app.js +++ b/public/app.js @@ -17,6 +17,8 @@ async function api(path, options = {}) { err.status = response.status if (data.code) err.code = data.code if (data.retryAfterSeconds) err.retryAfterSeconds = data.retryAfterSeconds + // Mitarbeiter-Login gesperrt: Adresse des Ticketsystems fuer den Link unter dem Hinweis + if (data.ticketUrl) err.ticketUrl = data.ticketUrl // Passwort auf einem anderen Gerät geändert: diese Sitzung gilt nicht mehr if (data.code === 'session_revoked' && document.body.classList.contains('dashboard-page') && !sessionEndRedirected) { sessionEndRedirected = true @@ -473,17 +475,12 @@ async function postLoginRedirect() { window.location.href = await resolvePostLoginTarget() } -// Links aus Admin-Mails (z. B. /admin.html?gutschrift=) nach dem Login wieder aufnehmen -function adminLoginTarget() { - const next = getQueryParam('next') - return /^\/admin\.html(\?[A-Za-z0-9=&_-]*)?$/.test(next) ? next : '/admin.html' -} - /* -------------------------------------------------------------- Login-Page --- */ async function initLoginPage() { const form = document.getElementById('login-form') const errorEl = document.getElementById('login-error') const btn = document.getElementById('login-btn') + const staffLink = document.getElementById('login-staff-link') const errorFromQuery = getQueryParam('error') if (errorFromQuery) { @@ -523,11 +520,8 @@ async function initLoginPage() { } try { + // Alte Mitarbeiter-Sitzung beendet /me selbst (reason staff): dann bleibt die Anmeldeseite stehen const me = await api('/api/auth/me') - if (me.authenticated && me.role === 'admin' && !me.viewAs) { - window.location.href = adminLoginTarget() - return - } if (me.authenticated && me.customer) { await postLoginRedirect() return @@ -540,6 +534,7 @@ async function initLoginPage() { e.preventDefault() if (btn.disabled) return errorEl.classList.add('hidden') + staffLink?.classList.add('hidden') btn.disabled = true btn.classList.add('is-loading') btn.setAttribute('aria-busy', 'true') @@ -552,18 +547,14 @@ async function initLoginPage() { password: document.getElementById('password').value, }), }) - if (result.mfaRequired) { - showMfaStep(result.email) - return - } - if (result.role === 'admin') { - window.location.href = adminLoginTarget() - return - } await postLoginRedirect() return } catch (err) { showError(errorEl, err.message) + if (err.ticketUrl && staffLink) { + staffLink.href = err.ticketUrl + staffLink.classList.remove('hidden') + } if (err.status === 429) { cooldownActive = true await applyCooldown(err.retryAfterSeconds || 900) @@ -576,59 +567,6 @@ async function initLoginPage() { }) } -/* Mitarbeiter-2FA: Code aus der E-Mail (POST /api/auth/login/mfa) */ -function showMfaStep(maskedEmail) { - const loginForm = document.getElementById('login-form') - const form = document.getElementById('mfa-form') - const input = document.getElementById('mfa-code') - const errorEl = document.getElementById('mfa-error') - const btn = document.getElementById('mfa-btn') - const resend = document.getElementById('mfa-resend') - loginForm?.classList.add('hidden') - form?.classList.remove('hidden') - document.getElementById('mfa-hint').textContent = `Wir haben dir einen Code an ${maskedEmail} geschickt.` - input?.focus() - - form?.addEventListener('submit', async (e) => { - e.preventDefault() - if (btn.disabled) return - hideError(errorEl) - btn.disabled = true - btn.classList.add('is-loading') - try { - await api('/api/auth/login/mfa', { method: 'POST', body: JSON.stringify({ otp: input.value.trim() }) }) - window.location.href = adminLoginTarget() - } catch (err) { - if (err.status === 410) { - window.location.href = `/login.html?error=${encodeURIComponent(err.message)}` - return - } - showError(errorEl, err.message) - input.select() - } finally { - btn.disabled = false - btn.classList.remove('is-loading') - } - }) - - resend?.addEventListener('click', async () => { - hideError(errorEl) - resend.disabled = true - try { - await api('/api/auth/login/mfa/resend', { method: 'POST' }) - document.getElementById('mfa-hint').textContent = `Neuer Code ist unterwegs an ${maskedEmail}.` - } catch (err) { - if (err.status === 410) { - window.location.href = `/login.html?error=${encodeURIComponent(err.message)}` - return - } - showError(errorEl, err.message) - } finally { - resend.disabled = false - } - }) -} - /* --------------------------------------------------------------- View-as --- */ function renderViewAsBanner(me) { const banner = document.getElementById('viewas-banner') @@ -2088,16 +2026,17 @@ async function initDashboardPage() { const me = await api('/api/auth/me') if (!me.authenticated) { + // Alte Mitarbeiter-Sitzung (Mitarbeiter arbeiten im Ticketsystem): dorthin weiter + if (me.reason === 'staff' && me.redirect) { + window.location.href = me.redirect + return + } const target = new URL(loginUrl, window.location.origin) // Passwort auf einem anderen Gerät geändert oder Kundenansicht abgelaufen: Hinweis auf der Anmeldeseite if ((me.reason === 'password_changed' || me.reason === 'view_expired') && me.message) target.searchParams.set('error', me.message) window.location.href = target.pathname + target.search return } - if (me.role === 'admin' && !me.customer) { - window.location.href = '/admin.html' - return - } if (!me.customer) { window.location.href = loginUrl return diff --git a/public/login.css b/public/login.css index 6478d32..fa6ab90 100644 --- a/public/login.css +++ b/public/login.css @@ -655,17 +655,12 @@ a.portal-btn-outline { } } -/* Mitarbeiter-2FA: Code-Schritt */ -#mfa-form.hidden { display: none; } -.mfa-hint { margin: 0 0 4px; text-align: center; } -#mfa-code { letter-spacing: 0.3em; font-variant-numeric: tabular-nums; } -.mfa-actions { margin-top: 12px; } -.mfa-link { - background: none; - border: 0; - padding: 0; - font: inherit; - color: inherit; - text-decoration: underline; - cursor: pointer; +/* Mitarbeiter-Login gesperrt: Link ins Ticketsystem unter dem Hinweis */ +.login-staff-link { + display: block; + margin: 8px 0 0; + text-align: center; + font-size: 0.875rem; + font-weight: 700; + color: #fff; } diff --git a/public/login.html b/public/login.html index bced9c3..cf14bab 100644 --- a/public/login.html +++ b/public/login.html @@ -62,6 +62,8 @@ enterkeyhint="go" > + + - -