829 Zeilen
21 KiB
JavaScript
829 Zeilen
21 KiB
JavaScript
/**
|
|
* TASKMATE - Authentication Module
|
|
* ================================
|
|
*/
|
|
|
|
import api from './api.js';
|
|
import { $, $$ } from './utils.js';
|
|
|
|
class AuthManager {
|
|
constructor() {
|
|
this.user = null;
|
|
this.isAuthenticated = false;
|
|
this.loginAttempts = 0;
|
|
this.maxLoginAttempts = 5;
|
|
this.lockoutDuration = 5 * 60 * 1000; // 5 minutes
|
|
this.lockoutUntil = null;
|
|
}
|
|
|
|
// Initialize authentication state
|
|
async init() {
|
|
const token = api.getToken();
|
|
console.log('[Auth] init() - Token exists:', !!token);
|
|
|
|
if (token) {
|
|
try {
|
|
// Verify token by making a request
|
|
console.log('[Auth] Verifying token...');
|
|
const users = await api.getUsers();
|
|
console.log('[Auth] Token valid, users loaded');
|
|
this.isAuthenticated = true;
|
|
|
|
// Get current user from stored data
|
|
const storedUser = localStorage.getItem('current_user');
|
|
if (storedUser) {
|
|
this.user = JSON.parse(storedUser);
|
|
}
|
|
|
|
return true;
|
|
} catch (error) {
|
|
// Token invalid
|
|
console.log('[Auth] Token invalid, logging out');
|
|
this.logout();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
console.log('[Auth] No token found');
|
|
return false;
|
|
}
|
|
|
|
// Login
|
|
async login(username, password) {
|
|
// Check lockout
|
|
if (this.isLockedOut()) {
|
|
const remainingTime = Math.ceil((this.lockoutUntil - Date.now()) / 1000);
|
|
throw new Error(`Zu viele Fehlversuche. Bitte warten Sie ${remainingTime} Sekunden.`);
|
|
}
|
|
|
|
try {
|
|
const response = await api.login(username, password);
|
|
|
|
this.user = response.user;
|
|
this.isAuthenticated = true;
|
|
this.loginAttempts = 0;
|
|
|
|
// Store user data
|
|
localStorage.setItem('current_user', JSON.stringify(response.user));
|
|
|
|
// Dispatch login event
|
|
window.dispatchEvent(new CustomEvent('auth:login', {
|
|
detail: { user: this.user }
|
|
}));
|
|
|
|
return response;
|
|
} catch (error) {
|
|
this.loginAttempts++;
|
|
|
|
if (this.loginAttempts >= this.maxLoginAttempts) {
|
|
this.lockoutUntil = Date.now() + this.lockoutDuration;
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Logout
|
|
async logout() {
|
|
try {
|
|
if (this.isAuthenticated) {
|
|
await api.logout();
|
|
}
|
|
} catch (error) {
|
|
// Ignore logout errors
|
|
} finally {
|
|
this.user = null;
|
|
this.isAuthenticated = false;
|
|
|
|
// Clear stored data
|
|
localStorage.removeItem('current_user');
|
|
localStorage.removeItem('auth_token');
|
|
|
|
// Dispatch logout event
|
|
window.dispatchEvent(new CustomEvent('auth:logout'));
|
|
}
|
|
}
|
|
|
|
// Change Password
|
|
async changePassword(currentPassword, newPassword) {
|
|
return api.changePassword(currentPassword, newPassword);
|
|
}
|
|
|
|
// Get Current User
|
|
getUser() {
|
|
return this.user;
|
|
}
|
|
|
|
// Check if logged in
|
|
isLoggedIn() {
|
|
return this.isAuthenticated && this.user !== null;
|
|
}
|
|
|
|
// Check lockout status
|
|
isLockedOut() {
|
|
if (!this.lockoutUntil) return false;
|
|
|
|
if (Date.now() >= this.lockoutUntil) {
|
|
this.lockoutUntil = null;
|
|
this.loginAttempts = 0;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Get user initials
|
|
getUserInitials() {
|
|
if (!this.user) return '?';
|
|
|
|
return this.user.username
|
|
.split(' ')
|
|
.map(part => part.charAt(0).toUpperCase())
|
|
.slice(0, 2)
|
|
.join('');
|
|
}
|
|
|
|
// Get user color
|
|
getUserColor() {
|
|
if (!this.user) return '#888888';
|
|
return this.user.color || '#00D4FF';
|
|
}
|
|
|
|
// Update user color
|
|
updateUserColor(color) {
|
|
if (this.user) {
|
|
this.user.color = color;
|
|
// Also update localStorage so color persists after refresh
|
|
localStorage.setItem('current_user', JSON.stringify(this.user));
|
|
}
|
|
}
|
|
|
|
// Check if user is admin
|
|
isAdmin() {
|
|
return this.user?.role === 'admin';
|
|
}
|
|
|
|
// Check if user has a specific permission
|
|
hasPermission(permission) {
|
|
if (!this.user) return false;
|
|
const permissions = this.user.permissions || [];
|
|
return permissions.includes(permission);
|
|
}
|
|
|
|
// Get user role
|
|
getRole() {
|
|
return this.user?.role || 'user';
|
|
}
|
|
|
|
// Get user permissions
|
|
getPermissions() {
|
|
return this.user?.permissions || [];
|
|
}
|
|
}
|
|
|
|
// Login Form Handler
|
|
class LoginFormHandler {
|
|
constructor(authManager) {
|
|
this.auth = authManager;
|
|
this.form = $('#login-form');
|
|
this.usernameInput = $('#login-username');
|
|
this.passwordInput = $('#login-password');
|
|
this.submitButton = this.form?.querySelector('button[type="submit"]');
|
|
this.errorMessage = $('#login-error');
|
|
|
|
this.bindEvents();
|
|
}
|
|
|
|
bindEvents() {
|
|
if (!this.form) return;
|
|
|
|
this.form.addEventListener('submit', (e) => this.handleSubmit(e));
|
|
|
|
// Enter key handling
|
|
this.passwordInput?.addEventListener('keypress', (e) => {
|
|
if (e.key === 'Enter') {
|
|
this.form.dispatchEvent(new Event('submit'));
|
|
}
|
|
});
|
|
|
|
// Clear error on input
|
|
[this.usernameInput, this.passwordInput].forEach(input => {
|
|
input?.addEventListener('input', () => this.clearError());
|
|
});
|
|
}
|
|
|
|
async handleSubmit(e) {
|
|
e.preventDefault();
|
|
|
|
const username = this.usernameInput?.value.trim();
|
|
const password = this.passwordInput?.value;
|
|
|
|
if (!username || !password) {
|
|
this.showError('Bitte Benutzername und Passwort eingeben.');
|
|
return;
|
|
}
|
|
|
|
this.setLoading(true);
|
|
this.clearError();
|
|
|
|
try {
|
|
await this.auth.login(username, password);
|
|
// Success - app will handle the redirect
|
|
} catch (error) {
|
|
this.showError(error.message || 'Anmeldung fehlgeschlagen.');
|
|
this.passwordInput.value = '';
|
|
this.passwordInput.focus();
|
|
} finally {
|
|
this.setLoading(false);
|
|
}
|
|
}
|
|
|
|
showError(message) {
|
|
if (this.errorMessage) {
|
|
this.errorMessage.textContent = message;
|
|
this.errorMessage.classList.remove('hidden');
|
|
}
|
|
}
|
|
|
|
clearError() {
|
|
if (this.errorMessage) {
|
|
this.errorMessage.textContent = '';
|
|
this.errorMessage.classList.add('hidden');
|
|
}
|
|
}
|
|
|
|
setLoading(loading) {
|
|
if (this.submitButton) {
|
|
this.submitButton.disabled = loading;
|
|
this.submitButton.classList.toggle('loading', loading);
|
|
}
|
|
|
|
if (this.usernameInput) this.usernameInput.disabled = loading;
|
|
if (this.passwordInput) this.passwordInput.disabled = loading;
|
|
}
|
|
|
|
reset() {
|
|
if (this.form) this.form.reset();
|
|
this.clearError();
|
|
this.setLoading(false);
|
|
}
|
|
}
|
|
|
|
// User Menu Handler
|
|
class UserMenuHandler {
|
|
constructor(authManager) {
|
|
this.auth = authManager;
|
|
this.userMenu = $('.user-menu');
|
|
this.userAvatar = $('#user-avatar');
|
|
this.userDropdown = $('.user-dropdown');
|
|
this.userName = $('#user-name');
|
|
this.userRole = $('#user-role');
|
|
this.logoutButton = $('#btn-logout');
|
|
this.changePasswordButton = $('#btn-change-password');
|
|
|
|
this.isOpen = false;
|
|
|
|
this.bindEvents();
|
|
}
|
|
|
|
bindEvents() {
|
|
// Toggle dropdown
|
|
this.userAvatar?.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
this.toggle();
|
|
});
|
|
|
|
// Close on outside click
|
|
document.addEventListener('click', (e) => {
|
|
if (!this.userMenu?.contains(e.target)) {
|
|
this.close();
|
|
}
|
|
});
|
|
|
|
// Logout
|
|
this.logoutButton?.addEventListener('click', () => this.handleLogout());
|
|
|
|
// Change password
|
|
this.changePasswordButton?.addEventListener('click', () => {
|
|
this.close();
|
|
window.dispatchEvent(new CustomEvent('modal:open', {
|
|
detail: { modalId: 'change-password-modal' }
|
|
}));
|
|
});
|
|
|
|
// Escape key
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape' && this.isOpen) {
|
|
this.close();
|
|
}
|
|
});
|
|
}
|
|
|
|
update() {
|
|
const user = this.auth.getUser();
|
|
|
|
if (this.userAvatar) {
|
|
this.userAvatar.textContent = this.auth.getUserInitials();
|
|
this.userAvatar.style.backgroundColor = this.auth.getUserColor();
|
|
}
|
|
|
|
if (this.userName) {
|
|
this.userName.textContent = user?.displayName || user?.username || 'Benutzer';
|
|
}
|
|
|
|
if (this.userRole) {
|
|
let roleText = 'Benutzer';
|
|
if (user?.role === 'admin') {
|
|
roleText = 'Administrator';
|
|
} else if (user?.permissions?.length > 0) {
|
|
roleText = user.permissions.map(p => p.charAt(0).toUpperCase() + p.slice(1)).join(', ');
|
|
}
|
|
this.userRole.textContent = roleText;
|
|
}
|
|
}
|
|
|
|
toggle() {
|
|
if (this.isOpen) {
|
|
this.close();
|
|
} else {
|
|
this.open();
|
|
}
|
|
}
|
|
|
|
open() {
|
|
if (this.userDropdown) {
|
|
this.userDropdown.classList.remove('hidden');
|
|
this.isOpen = true;
|
|
}
|
|
}
|
|
|
|
close() {
|
|
if (this.userDropdown) {
|
|
this.userDropdown.classList.add('hidden');
|
|
this.isOpen = false;
|
|
}
|
|
}
|
|
|
|
async handleLogout() {
|
|
this.close();
|
|
|
|
try {
|
|
await this.auth.logout();
|
|
} catch (error) {
|
|
console.error('Logout error:', error);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Session Timer Handler
|
|
class SessionTimerHandler {
|
|
constructor(authManager) {
|
|
this.auth = authManager;
|
|
this.timerElement = null;
|
|
this.countdownElement = null;
|
|
this.intervalId = null;
|
|
this.expiresAt = null;
|
|
this.warningThreshold = 60; // Warnung bei 60 Sekunden verbleibend
|
|
this.refreshDebounceTimer = null;
|
|
this.refreshDebounceDelay = 1000; // 1 Sekunde Debounce
|
|
this.isRefreshing = false;
|
|
this.isActive = false; // Nur aktiv wenn eingeloggt und Timer läuft
|
|
}
|
|
|
|
init() {
|
|
this.timerElement = $('#session-timer');
|
|
this.countdownElement = $('#session-countdown');
|
|
|
|
// Bei Login neu initialisieren
|
|
window.addEventListener('auth:login', () => {
|
|
// Kurze Verzögerung um sicherzustellen, dass Token gespeichert ist
|
|
setTimeout(() => {
|
|
this.updateFromToken();
|
|
this.start();
|
|
this.isActive = true;
|
|
}, 100);
|
|
});
|
|
|
|
// Bei Logout stoppen
|
|
window.addEventListener('auth:logout', () => {
|
|
this.isActive = false;
|
|
this.stop();
|
|
this.hide();
|
|
});
|
|
|
|
// Bei Interaktionen Session refreshen (mit Debouncing)
|
|
this.bindInteractionEvents();
|
|
}
|
|
|
|
// Interaktions-Events binden für Session-Refresh
|
|
bindInteractionEvents() {
|
|
const refreshOnInteraction = (e) => {
|
|
// Nicht refreshen wenn nicht aktiv (nicht eingeloggt oder Timer läuft nicht)
|
|
if (!this.isActive) return;
|
|
|
|
// Nicht refreshen bei Klicks auf Login-Formular
|
|
if (e.target.closest('#login-form') || e.target.closest('.login-container')) return;
|
|
|
|
// Nur refreshen wenn Token existiert
|
|
if (!localStorage.getItem('auth_token')) return;
|
|
|
|
// Debounce: Nur alle X ms refreshen
|
|
if (this.refreshDebounceTimer) {
|
|
clearTimeout(this.refreshDebounceTimer);
|
|
}
|
|
|
|
this.refreshDebounceTimer = setTimeout(() => {
|
|
this.refreshSession();
|
|
}, this.refreshDebounceDelay);
|
|
};
|
|
|
|
// Click-Events auf dem gesamten Dokument
|
|
document.addEventListener('click', refreshOnInteraction);
|
|
|
|
// Keyboard-Events
|
|
document.addEventListener('keydown', refreshOnInteraction);
|
|
}
|
|
|
|
// Session beim Server refreshen
|
|
async refreshSession() {
|
|
if (this.isRefreshing) return;
|
|
|
|
const token = localStorage.getItem('auth_token');
|
|
if (!token) return;
|
|
|
|
this.isRefreshing = true;
|
|
|
|
try {
|
|
const response = await fetch('/api/auth/refresh', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
if (data.token) {
|
|
// Wichtig: api.setToken() verwenden, um den Cache zu aktualisieren
|
|
api.setToken(data.token);
|
|
this.expiresAt = this.parseToken(data.token);
|
|
this.timerElement?.classList.remove('warning', 'critical');
|
|
|
|
// CSRF-Token auch aktualisieren
|
|
if (data.csrfToken) {
|
|
api.setCsrfToken(data.csrfToken);
|
|
}
|
|
}
|
|
} else if (response.status === 401) {
|
|
// Token ungültig - aber nur ausloggen wenn kein neuer Login stattfand
|
|
// (Race-Condition: Alter Refresh-Request kann 401 zurückgeben nachdem
|
|
// ein neuer Login erfolgreich war)
|
|
const currentToken = localStorage.getItem('auth_token');
|
|
if (currentToken === token) {
|
|
// Gleicher Token, wirklich ungültig, ausloggen
|
|
console.log('[Auth] Refresh returned 401, logging out');
|
|
this.auth.logout();
|
|
} else {
|
|
// Token hat sich geändert (neuer Login oder bereits ausgeloggt)
|
|
console.log('[Auth] Refresh 401 ignored - token changed (new login occurred)');
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Session refresh error:', error);
|
|
} finally {
|
|
this.isRefreshing = false;
|
|
}
|
|
}
|
|
|
|
// JWT-Token parsen und Ablaufzeit extrahieren
|
|
parseToken(token) {
|
|
if (!token) return null;
|
|
try {
|
|
const payload = token.split('.')[1];
|
|
const decoded = JSON.parse(atob(payload));
|
|
return decoded.exp ? decoded.exp * 1000 : null; // exp ist in Sekunden, wir brauchen ms
|
|
} catch (e) {
|
|
console.error('Token parsing error:', e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
updateFromToken() {
|
|
const token = localStorage.getItem('auth_token');
|
|
this.expiresAt = this.parseToken(token);
|
|
}
|
|
|
|
// Beim Seiten-Reload aufrufen
|
|
async initFromExistingSession() {
|
|
const token = localStorage.getItem('auth_token');
|
|
if (!token) {
|
|
this.hide();
|
|
return false;
|
|
}
|
|
|
|
// Prüfen ob Token noch gültig ist
|
|
const expiresAt = this.parseToken(token);
|
|
if (!expiresAt || expiresAt <= Date.now()) {
|
|
// Token abgelaufen
|
|
this.hide();
|
|
return false;
|
|
}
|
|
|
|
// Session refreshen um neues Token zu bekommen
|
|
this.isActive = true; // Aktivieren damit refreshSession funktioniert
|
|
await this.refreshSession();
|
|
|
|
// Timer mit neuem Token starten
|
|
this.updateFromToken();
|
|
if (this.expiresAt) {
|
|
this.start();
|
|
return true;
|
|
}
|
|
|
|
this.isActive = false;
|
|
return false;
|
|
}
|
|
|
|
start() {
|
|
this.stop(); // Bestehenden Timer stoppen
|
|
|
|
if (!this.expiresAt) {
|
|
this.hide();
|
|
return;
|
|
}
|
|
|
|
this.show();
|
|
this.update(); // Sofort updaten
|
|
|
|
// Jede Sekunde aktualisieren
|
|
this.intervalId = setInterval(() => this.update(), 1000);
|
|
}
|
|
|
|
stop() {
|
|
if (this.intervalId) {
|
|
clearInterval(this.intervalId);
|
|
this.intervalId = null;
|
|
}
|
|
}
|
|
|
|
update() {
|
|
if (!this.expiresAt || !this.countdownElement) return;
|
|
|
|
const now = Date.now();
|
|
const remaining = Math.max(0, this.expiresAt - now);
|
|
const seconds = Math.floor(remaining / 1000);
|
|
|
|
// Zeit formatieren
|
|
const minutes = Math.floor(seconds / 60);
|
|
const secs = seconds % 60;
|
|
const timeStr = `${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
|
|
|
|
this.countdownElement.textContent = timeStr;
|
|
|
|
// Warnung bei wenig Zeit
|
|
if (seconds <= this.warningThreshold && seconds > 0) {
|
|
this.timerElement?.classList.add('warning');
|
|
} else {
|
|
this.timerElement?.classList.remove('warning');
|
|
}
|
|
|
|
// Kritisch bei < 30 Sekunden
|
|
if (seconds <= 30 && seconds > 0) {
|
|
this.timerElement?.classList.add('critical');
|
|
} else {
|
|
this.timerElement?.classList.remove('critical');
|
|
}
|
|
|
|
// Session abgelaufen
|
|
if (seconds <= 0) {
|
|
this.stop();
|
|
this.handleExpired();
|
|
}
|
|
}
|
|
|
|
handleExpired() {
|
|
// Toast anzeigen
|
|
window.dispatchEvent(new CustomEvent('toast:show', {
|
|
detail: {
|
|
message: 'Sitzung abgelaufen. Bitte erneut anmelden.',
|
|
type: 'warning',
|
|
duration: 5000
|
|
}
|
|
}));
|
|
|
|
// Automatisch ausloggen
|
|
this.auth.logout();
|
|
}
|
|
|
|
show() {
|
|
this.timerElement?.classList.remove('hidden');
|
|
}
|
|
|
|
hide() {
|
|
this.timerElement?.classList.add('hidden');
|
|
if (this.countdownElement) {
|
|
this.countdownElement.textContent = '--:--';
|
|
}
|
|
}
|
|
|
|
// Token wurde erneuert (z.B. durch API-Response mit X-New-Token)
|
|
refreshToken(newToken) {
|
|
if (newToken) {
|
|
// api.setToken() wird bereits in api.js aufgerufen, hier nur Timer aktualisieren
|
|
this.expiresAt = this.parseToken(newToken);
|
|
this.timerElement?.classList.remove('warning', 'critical');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Change Password Modal Handler
|
|
class ChangePasswordHandler {
|
|
constructor(authManager) {
|
|
this.auth = authManager;
|
|
this.modal = $('#change-password-modal');
|
|
this.form = $('#change-password-form');
|
|
this.currentPassword = $('#current-password');
|
|
this.newPassword = $('#new-password');
|
|
this.confirmPassword = $('#confirm-password');
|
|
this.errorMessage = this.modal?.querySelector('.error-message');
|
|
this.submitButton = this.form?.querySelector('button[type="submit"]');
|
|
|
|
this.bindEvents();
|
|
}
|
|
|
|
bindEvents() {
|
|
this.form?.addEventListener('submit', (e) => this.handleSubmit(e));
|
|
|
|
// Password strength indicator
|
|
this.newPassword?.addEventListener('input', () => {
|
|
this.updatePasswordStrength();
|
|
});
|
|
|
|
// Confirm password validation
|
|
this.confirmPassword?.addEventListener('input', () => {
|
|
this.validateConfirmPassword();
|
|
});
|
|
}
|
|
|
|
async handleSubmit(e) {
|
|
e.preventDefault();
|
|
|
|
const currentPassword = this.currentPassword?.value;
|
|
const newPassword = this.newPassword?.value;
|
|
const confirmPassword = this.confirmPassword?.value;
|
|
|
|
// Validation
|
|
if (!currentPassword || !newPassword || !confirmPassword) {
|
|
this.showError('Bitte alle Felder ausfüllen.');
|
|
return;
|
|
}
|
|
|
|
if (newPassword.length < 8) {
|
|
this.showError('Das neue Passwort muss mindestens 8 Zeichen lang sein.');
|
|
return;
|
|
}
|
|
|
|
if (newPassword !== confirmPassword) {
|
|
this.showError('Die Passwörter stimmen nicht überein.');
|
|
return;
|
|
}
|
|
|
|
this.setLoading(true);
|
|
this.clearError();
|
|
|
|
try {
|
|
await this.auth.changePassword(currentPassword, newPassword);
|
|
|
|
// Success
|
|
this.reset();
|
|
this.closeModal();
|
|
|
|
window.dispatchEvent(new CustomEvent('toast:show', {
|
|
detail: {
|
|
message: 'Passwort erfolgreich geändert.',
|
|
type: 'success'
|
|
}
|
|
}));
|
|
} catch (error) {
|
|
this.showError(error.message || 'Fehler beim Ändern des Passworts.');
|
|
} finally {
|
|
this.setLoading(false);
|
|
}
|
|
}
|
|
|
|
updatePasswordStrength() {
|
|
const password = this.newPassword?.value || '';
|
|
const strengthIndicator = this.modal?.querySelector('.password-strength');
|
|
|
|
if (!strengthIndicator) return;
|
|
|
|
let strength = 0;
|
|
if (password.length >= 8) strength++;
|
|
if (password.length >= 12) strength++;
|
|
if (/[A-Z]/.test(password)) strength++;
|
|
if (/[a-z]/.test(password)) strength++;
|
|
if (/[0-9]/.test(password)) strength++;
|
|
if (/[^A-Za-z0-9]/.test(password)) strength++;
|
|
|
|
const levels = ['weak', 'fair', 'good', 'strong'];
|
|
const level = Math.min(Math.floor(strength / 1.5), 3);
|
|
|
|
strengthIndicator.className = `password-strength ${levels[level]}`;
|
|
strengthIndicator.dataset.strength = levels[level];
|
|
}
|
|
|
|
validateConfirmPassword() {
|
|
const newPassword = this.newPassword?.value;
|
|
const confirmPassword = this.confirmPassword?.value;
|
|
|
|
if (confirmPassword && newPassword !== confirmPassword) {
|
|
this.confirmPassword.setCustomValidity('Passwörter stimmen nicht überein');
|
|
} else {
|
|
this.confirmPassword.setCustomValidity('');
|
|
}
|
|
}
|
|
|
|
showError(message) {
|
|
if (this.errorMessage) {
|
|
this.errorMessage.textContent = message;
|
|
this.errorMessage.classList.remove('hidden');
|
|
}
|
|
}
|
|
|
|
clearError() {
|
|
if (this.errorMessage) {
|
|
this.errorMessage.textContent = '';
|
|
this.errorMessage.classList.add('hidden');
|
|
}
|
|
}
|
|
|
|
setLoading(loading) {
|
|
if (this.submitButton) {
|
|
this.submitButton.disabled = loading;
|
|
this.submitButton.classList.toggle('loading', loading);
|
|
}
|
|
}
|
|
|
|
reset() {
|
|
if (this.form) this.form.reset();
|
|
this.clearError();
|
|
this.setLoading(false);
|
|
}
|
|
|
|
closeModal() {
|
|
window.dispatchEvent(new CustomEvent('modal:close', {
|
|
detail: { modalId: 'change-password-modal' }
|
|
}));
|
|
}
|
|
}
|
|
|
|
// Create singleton instances
|
|
const authManager = new AuthManager();
|
|
let loginFormHandler = null;
|
|
let userMenuHandler = null;
|
|
let changePasswordHandler = null;
|
|
let sessionTimerHandler = null;
|
|
|
|
// Initialize handlers when DOM is ready
|
|
async function initAuthHandlers() {
|
|
loginFormHandler = new LoginFormHandler(authManager);
|
|
userMenuHandler = new UserMenuHandler(authManager);
|
|
changePasswordHandler = new ChangePasswordHandler(authManager);
|
|
sessionTimerHandler = new SessionTimerHandler(authManager);
|
|
sessionTimerHandler.init();
|
|
|
|
// Bei bestehendem Token: Session refreshen und Timer starten
|
|
const token = localStorage.getItem('auth_token');
|
|
if (token) {
|
|
await sessionTimerHandler.initFromExistingSession();
|
|
}
|
|
}
|
|
|
|
// Listen for DOM ready
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', initAuthHandlers);
|
|
} else {
|
|
initAuthHandlers();
|
|
}
|
|
|
|
// Listen for login event to update UI
|
|
window.addEventListener('auth:login', () => {
|
|
userMenuHandler?.update();
|
|
});
|
|
|
|
// Listen for token refresh event to update session timer
|
|
window.addEventListener('auth:token-refreshed', (e) => {
|
|
sessionTimerHandler?.refreshToken(e.detail?.token);
|
|
});
|
|
|
|
export {
|
|
authManager,
|
|
loginFormHandler,
|
|
userMenuHandler,
|
|
changePasswordHandler,
|
|
sessionTimerHandler
|
|
};
|
|
|
|
export default authManager;
|