From d26f8b0d93e42cc4cf7e19d6eee4f37d940d0c00 Mon Sep 17 00:00:00 2001 From: Till Tomczak Date: Thu, 29 May 2025 19:29:46 +0200 Subject: [PATCH] "feat: Implement auto logout functionality via static JS --- backend/app/app.py | 102 +++++++++++++++++++ backend/app/static/js/auto-logout.js | 143 +++++++++++++++++++++++++++ backend/app/templates/base.html | 1 + backend/app/templates/settings.html | 45 +++++---- 4 files changed, 268 insertions(+), 23 deletions(-) create mode 100644 backend/app/static/js/auto-logout.js diff --git a/backend/app/app.py b/backend/app/app.py index a3ad40b0..8bd5f050 100644 --- a/backend/app/app.py +++ b/backend/app/app.py @@ -5866,6 +5866,108 @@ def export_guest_requests(): # ===== ENDE ADMIN GASTAUFTRÄGE API-ENDPUNKTE ===== +@app.route("/api/user/settings/auto-logout", methods=["GET"]) +@login_required +def get_auto_logout_settings(): + """Holt nur die Auto-Logout-Einstellungen des Benutzers""" + try: + user_settings = session.get('user_settings', {}) + auto_logout = user_settings.get('privacy', {}).get('auto_logout', 60) + + return jsonify({ + "success": True, + "auto_logout": auto_logout + }) + + except Exception as e: + user_logger.error(f"Fehler beim Laden der Auto-Logout-Einstellungen: {str(e)}") + return jsonify({ + "success": False, + "error": "Fehler beim Laden der Auto-Logout-Einstellungen" + }), 500 + +@app.route("/api/user/setting", methods=["PATCH"]) +@login_required +def update_single_setting(): + """Aktualisiert eine einzelne Benutzereinstellung""" + try: + if not request.is_json: + return jsonify({"error": "Anfrage muss im JSON-Format sein"}), 400 + + data = request.get_json() + if not data: + return jsonify({"error": "Keine Daten erhalten"}), 400 + + # Aktuelle Einstellungen laden + user_settings = session.get('user_settings', { + "theme": "system", + "reduced_motion": False, + "contrast": "normal", + "notifications": { + "new_jobs": True, + "job_updates": True, + "system": True, + "email": False + }, + "privacy": { + "activity_logs": True, + "two_factor": False, + "auto_logout": 60 + } + }) + + # Einzelne Einstellung aktualisieren + for key, value in data.items(): + if key == "auto_logout": + # Validierung für Auto-Logout + try: + timeout = int(value) if value != "never" else "never" + if timeout != "never" and (timeout < 5 or timeout > 480): + return jsonify({"error": "Auto-Logout muss zwischen 5 und 480 Minuten liegen"}), 400 + user_settings.setdefault('privacy', {})['auto_logout'] = timeout + except (ValueError, TypeError): + return jsonify({"error": "Ungültiger Auto-Logout-Wert"}), 400 + + user_settings['last_updated'] = datetime.now().isoformat() + session['user_settings'] = user_settings + + user_logger.info(f"Benutzer {current_user.username} hat Einstellung '{key}' aktualisiert") + + return jsonify({ + "success": True, + "message": "Einstellung erfolgreich aktualisiert" + }) + + except Exception as e: + user_logger.error(f"Fehler beim Aktualisieren der Einzeleinstellung: {str(e)}") + return jsonify({ + "success": False, + "error": "Fehler beim Aktualisieren der Einstellung" + }), 500 + +@app.route("/api/auth/keep-alive", methods=["POST"]) +@login_required +def keep_alive(): + """Keep-Alive-Endpunkt für Auto-Logout-System""" + try: + # Session-Timestamp aktualisieren + session.permanent = True + session.modified = True + + auth_logger.info(f"Keep-Alive für Benutzer {current_user.username}") + + return jsonify({ + "success": True, + "message": "Session verlängert", + "timestamp": datetime.now().isoformat() + }) + + except Exception as e: + auth_logger.error(f"Fehler beim Keep-Alive: {str(e)}") + return jsonify({ + "success": False, + "error": "Fehler beim Verlängern der Session" + }), 500 # ===== STARTUP UND MAIN ===== if __name__ == "__main__": diff --git a/backend/app/static/js/auto-logout.js b/backend/app/static/js/auto-logout.js new file mode 100644 index 00000000..d36fc86e --- /dev/null +++ b/backend/app/static/js/auto-logout.js @@ -0,0 +1,143 @@ +class AutoLogoutManager { + constructor() { + this.timer = null; + this.warningTimer = null; + this.timeout = 60; // Standard: 60 Minuten + this.warningTime = 5; // Warnung 5 Minuten vor Logout + this.isWarningShown = false; + + this.init(); + } + + async init() { + await this.loadSettings(); + this.setupActivityListeners(); + this.startTimer(); + } + + async loadSettings() { + try { + const response = await fetch('/api/user/settings'); + if (response.ok) { + const data = await response.json(); + if (data.success && data.settings.privacy?.auto_logout) { + const timeout = parseInt(data.settings.privacy.auto_logout); + if (timeout > 0 && timeout !== 'never') { + this.timeout = timeout; + } else { + this.timeout = 0; // Deaktiviert + } + } + } + } catch (error) { + console.warn('Auto-Logout-Einstellungen konnten nicht geladen werden:', error); + } + } + + setupActivityListeners() { + const events = ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart', 'click']; + + events.forEach(event => { + document.addEventListener(event, () => this.resetTimer(), { passive: true }); + }); + } + + startTimer() { + if (this.timeout <= 0) return; + + this.clearTimers(); + + const timeoutMs = this.timeout * 60 * 1000; + const warningMs = this.warningTime * 60 * 1000; + + this.warningTimer = setTimeout(() => this.showWarning(), timeoutMs - warningMs); + this.timer = setTimeout(() => this.performLogout(), timeoutMs); + } + + resetTimer() { + if (this.isWarningShown) { + this.closeWarning(); + } + this.startTimer(); + } + + clearTimers() { + if (this.timer) clearTimeout(this.timer); + if (this.warningTimer) clearTimeout(this.warningTimer); + } + + showWarning() { + if (this.isWarningShown) return; + this.isWarningShown = true; + + const modal = document.createElement('div'); + modal.id = 'auto-logout-warning'; + modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50'; + modal.innerHTML = ` +
+

Automatische Abmeldung

+

+ Sie werden in ${this.warningTime} Minuten aufgrund von Inaktivität abgemeldet. +

+
+ + +
+
+ `; + + document.body.appendChild(modal); + + document.getElementById('stay-logged-in').onclick = () => { + this.closeWarning(); + this.sendKeepAlive(); + this.resetTimer(); + }; + + document.getElementById('logout-now').onclick = () => { + this.performLogout(); + }; + } + + closeWarning() { + const modal = document.getElementById('auto-logout-warning'); + if (modal) modal.remove(); + this.isWarningShown = false; + } + + async sendKeepAlive() { + try { + await fetch('/api/auth/keep-alive', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': this.getCSRFToken() + } + }); + } catch (error) { + console.warn('Keep-Alive fehlgeschlagen:', error); + } + } + + getCSRFToken() { + const metaTag = document.querySelector('meta[name="csrf-token"]'); + return metaTag ? metaTag.getAttribute('content') : ''; + } + + async performLogout() { + this.closeWarning(); + this.clearTimers(); + window.location.href = '/auth/logout'; + } +} + +// Initialisierung +document.addEventListener('DOMContentLoaded', function() { + if (!window.location.pathname.includes('/login')) { + window.autoLogoutManager = new AutoLogoutManager(); + } +}); \ No newline at end of file diff --git a/backend/app/templates/base.html b/backend/app/templates/base.html index 75d7ca38..ae753438 100644 --- a/backend/app/templates/base.html +++ b/backend/app/templates/base.html @@ -584,6 +584,7 @@ {% if current_user.is_authenticated %} + {% endif %} diff --git a/backend/app/templates/settings.html b/backend/app/templates/settings.html index 85632814..ba353f1d 100644 --- a/backend/app/templates/settings.html +++ b/backend/app/templates/settings.html @@ -621,33 +621,32 @@ function setupAutoLogout() { const autoLogoutSelect = document.getElementById('auto-logout'); - function resetLogoutTimer() { - if (logoutTimer) { - clearTimeout(logoutTimer); - } + // Event-Listener für Änderungen der Auto-Logout-Einstellung + autoLogoutSelect.addEventListener('change', async function() { + const newTimeout = this.value; - const minutes = parseInt(autoLogoutSelect.value); - if (minutes && minutes !== 'never') { - logoutTimer = setTimeout(() => { - if (confirm('Sie werden aufgrund von Inaktivität abgemeldet. Möchten Sie angemeldet bleiben?')) { - resetLogoutTimer(); - } else { - window.location.href = '/logout'; + try { + const response = await fetch('/api/user/setting', { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '' + }, + body: JSON.stringify({ auto_logout: newTimeout }) + }); + + if (response.ok) { + // Globales Auto-Logout-System benachrichtigen + if (window.autoLogoutManager) { + window.autoLogoutManager.updateSettings(newTimeout); } - }, minutes * 60 * 1000); + showFlashMessage('Auto-Logout-Einstellung aktualisiert', 'success'); + } + } catch (error) { + console.error('Fehler beim Aktualisieren der Auto-Logout-Einstellung:', error); + showFlashMessage('Fehler beim Speichern der Einstellung', 'error'); } - } - - // Reset timer on any user activity - ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart', 'click'].forEach(event => { - document.addEventListener(event, resetLogoutTimer, { passive: true }); }); - - // Initial setup - resetLogoutTimer(); - - // Update timer when setting changes - autoLogoutSelect.addEventListener('change', resetLogoutTimer); } // Enhanced toggle switches with keyboard support