All new
alle new
This commit is contained in:
1 parent
da64472251
commit
5b20adee48
1605 files changed
+396517
-1674
No files matched your search
@@ -0,0 +1,196 @@
|
||||
// -----------------------------
|
||||
// Projektübersicht — Authentifizierung (Login-Overlay, Session, Logout)
|
||||
// -----------------------------
|
||||
// Wird von Test.html direkt nach
|
||||
// js/server.js geladen, da checkAuthAndInit() baseUrl() benötigt. Ersetzt den
|
||||
// früheren rein clientseitigen Passwortschutz (js/tabs-auth.js) durch eine
|
||||
// echte serverseitige Session (server.js: /login, /logout, /whoami). Ohne
|
||||
// gültige Session zeigt checkAuthAndInit() das blockierende Login-Overlay
|
||||
// (#loginOverlay/#loginForm, siehe Test.html); nach
|
||||
// erfolgreichem Login läuft der eigentliche Seiteneinstieg (per Callback
|
||||
// übergeben) wie gewohnt weiter.
|
||||
//
|
||||
// Hier liegt außerdem der Dialog zum Ändern des eigenen Passworts
|
||||
// (#passwordModal, POST /change-password). Er hat zwei Betriebsarten:
|
||||
// freiwillig (aus dem Bereich „Einstellungen" heraus, mit Abbrechen) und
|
||||
// ERZWUNGEN — wenn das Konto must_change_password trägt, was der Server bei
|
||||
// jedem neu angelegten Konto und nach jedem Zurücksetzen setzt. Bis zu diesem
|
||||
// Umbau gab es zu dem Endpunkt gar keine Oberfläche: Die Benutzerverwaltung
|
||||
// zeigte „Passwortwechsel erforderlich" an, erfüllen konnte ihn niemand.
|
||||
|
||||
// currentUser: der aktuell angemeldete Benutzer ({id, username, displayName,
|
||||
// email, role, personId, mustChangePassword}) oder null vor dem Login.
|
||||
let currentUser = null;
|
||||
|
||||
// renderUserBar: zeigt Namen + Rolle des angemeldeten Benutzers im Menueband
|
||||
// an (Element #userBarInfo) und blendet den gesamten Admin-Bereich der
|
||||
// Seitenleiste (#navAdminSection) nur fuer Administratoren ein. Dieser
|
||||
// Bereich fuehrt zu allen Admin-Aufgaben — Benutzerverwaltung, firmenweite
|
||||
// Vorgaben, Datenbank und Sitzungen. Es ist ein reines UI-Gate; die
|
||||
// eigentliche Sperre sitzt serverseitig in requireRole('admin') auf den
|
||||
// zugehoerigen Routen, und showTab() leitet Nicht-Administratoren zusaetzlich
|
||||
// vom Bereich „Admin" weg (js/tabs-auth.js).
|
||||
function renderUserBar(){
|
||||
const info = document.getElementById('userBarInfo');
|
||||
if(info && currentUser){
|
||||
const roleLabel = currentUser.role === 'admin' ? 'Administrator' : 'Mitglied';
|
||||
info.textContent = (currentUser.displayName || currentUser.username) + ' (' + roleLabel + ')';
|
||||
}
|
||||
const navAdminSection = document.getElementById('navAdminSection');
|
||||
if(navAdminSection) navAdminSection.style.display = (currentUser && currentUser.role === 'admin') ? '' : 'none';
|
||||
}
|
||||
|
||||
// logout: beendet die Server-Session und lädt die Seite neu (zeigt danach
|
||||
// wieder das Login-Overlay, da /whoami dann {user:null} liefert).
|
||||
async function logout(){
|
||||
try { await fetch(baseUrl() + '/logout', { method:'POST' }); } catch(e){}
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
// showLoginOverlay / hideLoginOverlay: blenden das blockierende Login-Overlay ein/aus.
|
||||
function showLoginOverlay(){
|
||||
document.getElementById('loginOverlay').style.display = 'flex';
|
||||
}
|
||||
function hideLoginOverlay(){
|
||||
document.getElementById('loginOverlay').style.display = 'none';
|
||||
}
|
||||
|
||||
// afterAuthenticated: gemeinsamer Nachlauf beider Anmeldewege (bestehende
|
||||
// Session laut /whoami bzw. frisch abgeschicktes Login-Formular). Startet die
|
||||
// Seite und erzwingt danach — erst danach, damit die Anwendung hinter dem
|
||||
// Dialog schon steht — den Passwortwechsel, falls das Konto ihn verlangt.
|
||||
// Parameter: onAuthenticated (Funktion). Keine Rückgabe.
|
||||
function afterAuthenticated(onAuthenticated){
|
||||
hideLoginOverlay();
|
||||
renderUserBar();
|
||||
onAuthenticated();
|
||||
if(currentUser && currentUser.mustChangePassword) openPasswordModal(true);
|
||||
}
|
||||
|
||||
// ---------- Eigenes Passwort ändern (#passwordModal) ----------
|
||||
|
||||
// passwordChangeForced: true, solange der Dialog wegen must_change_password
|
||||
// offen ist. Dann gibt es kein Abbrechen — sonst bliebe die Kennzeichnung im
|
||||
// Konto stehen und das (oft per Zettel weitergegebene) Startpasswort gültig.
|
||||
let passwordChangeForced = false;
|
||||
|
||||
// openPasswordModal: zeigt den Dialog. Parameter: forced (Boolean) — im
|
||||
// erzwungenen Fall ohne Abbrechen-Schaltfläche und mit Hinweistext.
|
||||
// Keine Rückgabe.
|
||||
function openPasswordModal(forced){
|
||||
const modal = document.getElementById('passwordModal');
|
||||
if(!modal) return;
|
||||
passwordChangeForced = !!forced;
|
||||
|
||||
['passwordCurrent','passwordNew','passwordRepeat'].forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
if(el) el.value = '';
|
||||
});
|
||||
const errEl = document.getElementById('passwordError');
|
||||
if(errEl){ errEl.textContent = ''; errEl.hidden = true; }
|
||||
|
||||
const forcedHint = document.getElementById('passwordForcedHint');
|
||||
if(forcedHint) forcedHint.hidden = !passwordChangeForced;
|
||||
const cancelBtn = document.getElementById('passwordCancelBtn');
|
||||
if(cancelBtn) cancelBtn.style.display = passwordChangeForced ? 'none' : '';
|
||||
|
||||
modal.style.display = 'flex';
|
||||
const first = document.getElementById('passwordCurrent');
|
||||
if(first) first.focus();
|
||||
}
|
||||
|
||||
// closePasswordModal: schließt den Dialog — im erzwungenen Fall verweigert er
|
||||
// sich. Keine Parameter, keine Rückgabe.
|
||||
function closePasswordModal(){
|
||||
if(passwordChangeForced) return;
|
||||
const modal = document.getElementById('passwordModal');
|
||||
if(modal) modal.style.display = 'none';
|
||||
}
|
||||
|
||||
// submitPasswordChange: schickt den Wechsel an POST /change-password. Der
|
||||
// Server verlangt immer das aktuelle Passwort (auch im erzwungenen Fall) und
|
||||
// mindestens 8 Zeichen; die Wiederholung prüft nur der Client, damit ein
|
||||
// Vertipper nicht erst nach dem Speichern auffällt.
|
||||
// Parameter: ev (Submit-Event). Rückgabe: keine.
|
||||
async function submitPasswordChange(ev){
|
||||
if(ev) ev.preventDefault();
|
||||
const errEl = document.getElementById('passwordError');
|
||||
const show = msg => { if(errEl){ errEl.textContent = msg; errEl.hidden = false; } };
|
||||
|
||||
const currentPassword = (document.getElementById('passwordCurrent') || {}).value || '';
|
||||
const newPassword = (document.getElementById('passwordNew') || {}).value || '';
|
||||
const repeat = (document.getElementById('passwordRepeat') || {}).value || '';
|
||||
if(errEl){ errEl.textContent = ''; errEl.hidden = true; }
|
||||
|
||||
if(newPassword.length < 8) return show('Das neue Passwort muss mindestens 8 Zeichen haben.');
|
||||
if(newPassword !== repeat) return show('Die Wiederholung stimmt nicht mit dem neuen Passwort überein.');
|
||||
if(newPassword === currentPassword) return show('Das neue Passwort muss sich vom bisherigen unterscheiden.');
|
||||
|
||||
try {
|
||||
const r = await fetch(baseUrl() + '/change-password', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
||||
body: JSON.stringify({ currentPassword, newPassword })
|
||||
});
|
||||
const d = await r.json();
|
||||
if(!r.ok || !d.ok) return show(d && d.error ? d.error : 'Passwort konnte nicht geändert werden.');
|
||||
} catch(err){
|
||||
console.error('Passwortwechsel fehlgeschlagen:', err);
|
||||
return show('Verbindung zum Server fehlgeschlagen.');
|
||||
}
|
||||
|
||||
if(currentUser) currentUser.mustChangePassword = false;
|
||||
passwordChangeForced = false;
|
||||
closePasswordModal();
|
||||
// Der Bereich „Einstellungen" zeigt die Kennzeichnung mit an.
|
||||
if(typeof renderSettings === 'function' && typeof currentTab !== 'undefined' && currentTab === 'settings') renderSettings();
|
||||
alert('Das Passwort wurde geändert.');
|
||||
}
|
||||
|
||||
// checkAuthAndInit: prüft per GET /whoami, ob eine gültige Session besteht.
|
||||
// Falls ja, wird currentUser gesetzt, das Overlay ausgeblendet und der
|
||||
// übergebene Callback (der eigentliche Seiteneinstieg) ausgeführt. Falls
|
||||
// nein, wird das Login-Overlay gezeigt und das Login-Formular gebunden; nach
|
||||
// erfolgreichem Login läuft derselbe Callback an. Parameter: onAuthenticated
|
||||
// (Funktion ohne Argumente).
|
||||
async function checkAuthAndInit(onAuthenticated){
|
||||
let data = null;
|
||||
try {
|
||||
const res = await fetch(baseUrl() + '/whoami');
|
||||
data = await res.json();
|
||||
} catch(err){
|
||||
// Ohne diesen Zweig blieb bei nicht erreichbarem Server (oder einer
|
||||
// HTML-Antwort einer Zwischeninstanz) eine leere, tote Seite stehen: Das
|
||||
// Promise wurde abgelehnt, das Login-Fenster nie gezeigt.
|
||||
console.error('Anmeldestatus konnte nicht geprüft werden:', err);
|
||||
alert('Der Server unter ' + baseUrl() + ' ist nicht erreichbar.\n\n'
|
||||
+ 'Bitte prüfen, ob er läuft, und die Seite anschließend neu laden.');
|
||||
return;
|
||||
}
|
||||
if(data && data.user){
|
||||
currentUser = data.user;
|
||||
afterAuthenticated(onAuthenticated);
|
||||
return;
|
||||
}
|
||||
showLoginOverlay();
|
||||
const form = document.getElementById('loginForm');
|
||||
const errEl = document.getElementById('loginError');
|
||||
form.onsubmit = async function(ev){
|
||||
ev.preventDefault();
|
||||
errEl.textContent = '';
|
||||
const username = document.getElementById('loginUsername').value;
|
||||
const password = document.getElementById('loginPassword').value;
|
||||
try {
|
||||
const r = await fetch(baseUrl() + '/login', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ username, password })
|
||||
});
|
||||
const d = await r.json();
|
||||
if(!r.ok || !d.ok){ errEl.textContent = d.error || 'Anmeldung fehlgeschlagen.'; return; }
|
||||
currentUser = d.user;
|
||||
afterAuthenticated(onAuthenticated);
|
||||
} catch(err){
|
||||
errEl.textContent = 'Verbindung zum Server fehlgeschlagen.';
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user