"feat: Implement auto logout functionality via static JS

This commit is contained in:
Till Tomczak 2025-05-29 19:29:46 +02:00
parent f4fbf92055
commit d26f8b0d93
4 changed files with 268 additions and 23 deletions

View File

@ -5866,6 +5866,108 @@ def export_guest_requests():
# ===== ENDE ADMIN GASTAUFTRÄGE API-ENDPUNKTE ===== # ===== 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 ===== # ===== STARTUP UND MAIN =====
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -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 = `
<div class="bg-white dark:bg-slate-800 rounded-lg p-6 max-w-md mx-4 shadow-xl">
<h3 class="text-lg font-medium text-slate-900 dark:text-white mb-4">Automatische Abmeldung</h3>
<p class="text-sm text-slate-600 dark:text-slate-300 mb-4">
Sie werden in ${this.warningTime} Minuten aufgrund von Inaktivität abgemeldet.
</p>
<div class="flex space-x-3">
<button id="stay-logged-in" class="bg-blue-600 text-white px-4 py-2 rounded-lg">
Angemeldet bleiben
</button>
<button id="logout-now" class="bg-gray-300 text-slate-700 px-4 py-2 rounded-lg">
Jetzt abmelden
</button>
</div>
</div>
`;
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();
}
});

View File

@ -584,6 +584,7 @@
<script src="{{ url_for('static', filename='js/csp-violation-handler.js') }}"></script> <script src="{{ url_for('static', filename='js/csp-violation-handler.js') }}"></script>
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
<script src="{{ url_for('static', filename='js/notifications.js') }}"></script> <script src="{{ url_for('static', filename='js/notifications.js') }}"></script>
<script src="{{ url_for('static', filename='js/auto-logout.js') }}"></script>
{% endif %} {% endif %}
<!-- Additional JavaScript Functions --> <!-- Additional JavaScript Functions -->

View File

@ -621,33 +621,32 @@
function setupAutoLogout() { function setupAutoLogout() {
const autoLogoutSelect = document.getElementById('auto-logout'); const autoLogoutSelect = document.getElementById('auto-logout');
function resetLogoutTimer() { // Event-Listener für Änderungen der Auto-Logout-Einstellung
if (logoutTimer) { autoLogoutSelect.addEventListener('change', async function() {
clearTimeout(logoutTimer); const newTimeout = this.value;
}
const minutes = parseInt(autoLogoutSelect.value); try {
if (minutes && minutes !== 'never') { const response = await fetch('/api/user/setting', {
logoutTimer = setTimeout(() => { method: 'PATCH',
if (confirm('Sie werden aufgrund von Inaktivität abgemeldet. Möchten Sie angemeldet bleiben?')) { headers: {
resetLogoutTimer(); 'Content-Type': 'application/json',
} else { 'X-CSRFToken': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''
window.location.href = '/logout'; },
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 // Enhanced toggle switches with keyboard support