diff --git a/DEPLOY.md b/DEPLOY.md index 2ac78c2..a406555 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -265,6 +265,23 @@ Wichtig in beiden Fällen: * `/health` ist der Endpunkt für Health-Checks (antwortet **ohne** Anmeldung) * Websockets werden **nicht** gebraucht +### Portal-Kopfleiste + +`frontend/index.html` lädt die zentrale Handy-Insel-Kopfleiste +(`https://portal.handy-insel.de/topbar.js`, `data-app="timer"`). Sie bringt +App-Umschalter und Benutzer-Menü mit; Benutzername und „Abmelden“ der App +stehen deshalb dort, die eigenen im Header erscheinen nur, wenn das Skript +nicht lädt. + +* **Abmelden** in der Leiste ruft vorher `POST /api/logout` auf (Ereignis + `hi-abmelden`) und meldet danach bei Portal und Authentik ab. +* **Launch-URL** der Kachel im Portal: `https://timer.handy-insel.de/auth/login` + (startet direkt die Anmeldung über Authentik). +* **Content-Security-Policy:** Die App setzt keine. Kommt im Proxy eine dazu, + muss sie `script-src`/`connect-src https://portal.handy-insel.de` und + `img-src https://portal.handy-insel.de https://login.handy-insel.de` + erlauben (Details in den Proxy-Beispielen). + ### Bondrucker Vollständige Einrichtung: **`deploy/bondrucker/README.md`**. Hier nur das diff --git a/README.md b/README.md index ee2e17e..20099ed 100644 --- a/README.md +++ b/README.md @@ -153,5 +153,10 @@ der Bondrucker hängt am USB-Anschluss. ## Abmelden -**🚪 Abmelden** meldet nur dieses Gerät ab. Laufende Timer bleiben davon -unberührt und laufen weiter. +Ganz oben steht die Handy-Insel-Leiste. Über den Avatar rechts öffnet sich das +Benutzer-Menü, dort steht **Abmelden**. Das meldet dieses Gerät beim Timer, im +Portal und bei Authentik ab. Laufende Timer bleiben davon unberührt und laufen +weiter. Laufen noch welche, fragt die App vorher nach. + +Ist das Portal einmal nicht erreichbar, fehlt die Leiste. Dann steht +**🚪 Abmelden** wie früher oben rechts in der App. diff --git a/backend/server.js b/backend/server.js index 4edb9d8..3ccb8ae 100644 --- a/backend/server.js +++ b/backend/server.js @@ -487,9 +487,14 @@ app.get('/auth/logout', (req, res) => { }); }); -// Passwort-Logout (Fallback, wird per fetch aufgerufen) +// Logout per fetch: beendet nur die lokale Sitzung. Aufgerufen von der +// Portal-Kopfleiste (Ereignis "hi-abmelden"), die danach selbst bei Portal +// und Authentik abmeldet. app.post('/api/logout', (req, res) => { - req.session.destroy(() => res.json({ success: true })); + req.session.destroy(() => { + res.clearCookie('connect.sid'); + res.json({ success: true }); + }); }); // --------------------------------------------------------------------------- diff --git a/frontend/app.js b/frontend/app.js index 3bf3954..0c451e6 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -161,28 +161,71 @@ async function enterApp() { startLoops(); } -// Zeigt den angemeldeten Authentik-Nutzer oben rechts im Header. +// Zeigt den angemeldeten Authentik-Nutzer: im Benutzer-Menue der +// Portal-Kopfleiste, ohne Leiste oben rechts im eigenen Header. function renderCurrentUser() { - const el = document.getElementById('currentUser'); - if (!el) return; const u = loggedInUser; - el.textContent = u ? (u.displayName || u.username || '') : ''; + const name = u ? (u.displayName || u.username || '') : ''; + const el = document.getElementById('currentUser'); + el.textContent = name; el.title = u && u.email ? u.email : 'Angemeldet'; + renderTopbarSession(name); } -async function logout() { +function confirmLogout() { const running = state.timers.filter(t => t.isRunning).length; - if (running > 0 && !confirm( + return running === 0 || confirm( `${running} Timer ${running === 1 ? 'läuft' : 'laufen'} noch.\n\n` + `Sie laufen serverseitig weiter und sind nach dem nächsten Anmelden wieder da. Trotzdem abmelden?` - )) return; + ); +} +// Eigener Abmelden-Knopf, nur sichtbar ohne Portal-Kopfleiste +async function logout() { + if (!confirmLogout()) return; stopLoops(); state.timers = []; // GET-Navigation: beendet lokale Sitzung und – bei Authentik-Login – auch die SSO-Sitzung window.location.href = '/auth/logout'; } +// --------------------------------------------------------------------------- +// Portal-Kopfleiste +// --------------------------------------------------------------------------- + +// topbar.js laeuft per defer erst nach diesem Skript, die Leiste steht also +// fruehestens ab DOMContentLoaded im DOM. Die Sitzungsanzeige wird einmal +// eingehaengt, sobald die Anmeldung steht, danach aendert sich nur ihr Text. +const topbarSession = document.createElement('div'); +topbarSession.slot = 'benutzer'; +topbarSession.className = 'topbar-session'; +topbarSession.append(Object.assign(document.createElement('span'), { textContent: 'Timer – angemeldet als' }), + document.createElement('strong')); +let topbarSessionName = null; // null = (noch) nicht angemeldet + +function renderTopbarSession(name) { + topbarSessionName = name || 'Angemeldet'; + topbarSession.lastChild.textContent = topbarSessionName; + if (!topbarSession.isConnected) document.querySelector('hi-topbar')?.append(topbarSession); +} + +document.addEventListener('DOMContentLoaded', () => { + if (topbarSessionName !== null) renderTopbarSession(topbarSessionName); +}); + +// "Abmelden" in der Leiste: erst die eigene Sitzung beenden, danach meldet +// die Leiste bei Portal und Authentik ab. Abbrechen bei laufenden Timern +// verhindert das ganze Abmelden. +document.addEventListener('hi-abmelden', (e) => { + if (!confirmLogout()) { + e.preventDefault(); + return; + } + stopLoops(); + state.timers = []; + e.detail.warten(fetch('/api/logout', { method: 'POST', credentials: 'same-origin' })); +}); + // --------------------------------------------------------------------------- // Timer-Synchronisation // --------------------------------------------------------------------------- diff --git a/frontend/index.html b/frontend/index.html index d6d96b2..224b86b 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,5 +1,6 @@ - + +
@@ -795,6 +796,15 @@ soll sie nicht erscheinen. */ hi-topbar { flex-shrink: 0; } @media print { hi-topbar { display: none !important; } } + + /* Benutzername und Abmelden sitzen im Benutzer-Menue der Leiste. Die + eigenen bleiben nur als Rueckfall, wenn topbar.js nicht laedt. */ + hi-topbar ~ .container #currentUser, + hi-topbar ~ .container #logoutBtn { display: none; } + + /* Sitzungsanzeige im Benutzer-Menue (slot="benutzer"), Schriftfarbe kommt von der Leiste */ + .topbar-session { padding: 10px 16px; font-size: 13px; line-height: 1.4; } + .topbar-session span { display: block; opacity: 0.7; } @@ -806,7 +816,7 @@ - + @@ -1019,6 +1029,6 @@ - +