Portal: Mitarbeiter-Login gesperrt (Hinweis aufs Ticketsystem), alte Mitarbeiter-Sitzungen enden

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-02 03:44:10 +00:00
1 parent d0ca3c41cd
commit 3a4fa581c0
13 files changed
+145 -559

No files matched your search

+14 -75
View File
@@ -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=<id>) 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
+8 -13
View File
@@ -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;
}
+2 -25
View File
@@ -62,6 +62,8 @@
enterkeyhint="go"
>
<p id="login-error" class="error hidden"></p>
<!-- Mitarbeiter-Login ist gesperrt: Link ins Ticketsystem (Adresse aus der Antwort, ticketUrl) -->
<a id="login-staff-link" class="login-staff-link hidden" href="https://ticket.webklar.com">Zum Ticketsystem</a>
<button type="submit" id="login-btn" class="auth-button login-button">
<span class="button-label">Anmelden</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 11 10" aria-hidden="true">
@@ -72,31 +74,6 @@
</svg>
</button>
</form>
<!-- Mitarbeiter mit 2FA: zweiter Schritt, Code aus der E-Mail -->
<form id="mfa-form" class="login-form hidden" novalidate>
<p id="mfa-hint" class="login-hint mfa-hint"></p>
<label class="auth-label" for="mfa-code">Code aus der E-Mail</label>
<input
class="auth-input"
type="text"
name="otp"
id="mfa-code"
inputmode="numeric"
autocomplete="one-time-code"
maxlength="6"
pattern="[0-9]{6}"
enterkeyhint="go"
>
<p id="mfa-error" class="error hidden"></p>
<button type="submit" id="mfa-btn" class="auth-button login-button">
<span class="button-label">Anmelden</span>
</button>
<p class="login-hint mfa-actions">
<button type="button" id="mfa-resend" class="mfa-link">Code erneut senden</button>
·
<a href="/login.html" class="mfa-link">Abbrechen</a>
</p>
</form>
</div>
<p class="login-hint">
Zugangsdaten erhältst du von Webklar. Dein Passwort kannst du nach der Anmeldung selbst ändern (Profilbild oben rechts, „Passwort ändern“).