Diverse anpassungen
This commit is contained in:
@@ -3,8 +3,8 @@
|
||||
- Offset für die Verwaltung für Urlaubstage -> DONE
|
||||
- Stunden pro Tag und wie viele Tage arbeit -> DONE
|
||||
- Reisen für Wochenende -> DONE
|
||||
- LDAP Prüfung
|
||||
- DSGVO Sicherheit
|
||||
- LDAP Prüfung -> DONE TESTEn mit Jessi und Jörg
|
||||
- DSGVO Sicherheit -> DONE
|
||||
- Feiertage müssen als ausgefüllt zählen -> DONE
|
||||
- Mitarbeiter sollen PDF ansehen können. -> DONE
|
||||
- Wenn bereits heruntergeladen wurde und neue version da ist Meldung an Verwaltung. -> DONE Muss getestet werden
|
||||
|
||||
@@ -914,26 +914,25 @@ function handleOvertimeChange(dateStr, overtimeHours) {
|
||||
const fullDayHours = getFullDayHours();
|
||||
const overtimeValue = parseFloat(overtimeHours) || 0;
|
||||
|
||||
// Entferne "Überstunden" aus Activity-Feldern falls vorhanden
|
||||
// (Überstunden werden nur im PDF angezeigt, nicht als Tätigkeit)
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const descInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${i}_desc"]`);
|
||||
const hoursInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${i}_hours"]`);
|
||||
|
||||
if (descInput && descInput.value && descInput.value.trim().toLowerCase() === 'überstunden') {
|
||||
descInput.value = '';
|
||||
saveEntry(descInput);
|
||||
if (hoursInput) {
|
||||
hoursInput.value = '';
|
||||
saveEntry(hoursInput);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prüfe ob ganzer Tag Überstunden
|
||||
if (overtimeValue > 0 && Math.abs(overtimeValue - fullDayHours) < 0.01) {
|
||||
// Ganzer Tag Überstunden
|
||||
// Setze Activity1 auf "Überstunden" mit 0 Stunden
|
||||
const activity1DescInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity1_desc"]`);
|
||||
const activity1HoursInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity1_hours"]`);
|
||||
|
||||
if (activity1DescInput) {
|
||||
activity1DescInput.value = 'Überstunden';
|
||||
// Trigger saveEntry für dieses Feld
|
||||
saveEntry(activity1DescInput);
|
||||
}
|
||||
|
||||
if (activity1HoursInput) {
|
||||
activity1HoursInput.value = '0';
|
||||
// Trigger saveEntry für dieses Feld
|
||||
saveEntry(activity1HoursInput);
|
||||
}
|
||||
|
||||
// Leere Start- und End-Zeit
|
||||
// Ganzer Tag Überstunden - leere Start- und End-Zeit
|
||||
const startInput = document.querySelector(`input[data-date="${dateStr}"][data-field="start_time"]`);
|
||||
const endInput = document.querySelector(`input[data-date="${dateStr}"][data-field="end_time"]`);
|
||||
|
||||
@@ -946,41 +945,9 @@ function handleOvertimeChange(dateStr, overtimeHours) {
|
||||
endInput.value = '';
|
||||
saveEntry(endInput);
|
||||
}
|
||||
|
||||
} else if (overtimeValue > 0 && overtimeValue < fullDayHours) {
|
||||
// Weniger als ganzer Tag - füge "Überstunden" als Tätigkeit hinzu
|
||||
// Finde erste freie Activity-Spalte oder prüfe ob bereits vorhanden
|
||||
let foundOvertime = false;
|
||||
let firstEmptySlot = null;
|
||||
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const descInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${i}_desc"]`);
|
||||
const hoursInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${i}_hours"]`);
|
||||
|
||||
if (descInput && descInput.value && descInput.value.trim().toLowerCase() === 'überstunden') {
|
||||
foundOvertime = true;
|
||||
break; // Bereits vorhanden
|
||||
}
|
||||
|
||||
if (!firstEmptySlot && descInput && (!descInput.value || descInput.value.trim() === '')) {
|
||||
firstEmptySlot = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Wenn nicht gefunden und freier Slot vorhanden, füge hinzu
|
||||
if (!foundOvertime && firstEmptySlot) {
|
||||
const descInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${firstEmptySlot}_desc"]`);
|
||||
const hoursInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${firstEmptySlot}_hours"]`);
|
||||
|
||||
if (descInput) {
|
||||
descInput.value = 'Überstunden';
|
||||
saveEntry(descInput);
|
||||
}
|
||||
|
||||
// Stunden bleiben unverändert (werden vom User eingegeben oder bleiben leer)
|
||||
// total_hours bleibt auch unverändert
|
||||
}
|
||||
}
|
||||
// Bei weniger als ganzer Tag oder keine Überstunden: keine weiteren Aktionen
|
||||
// Überstunden werden nur im PDF als Information angezeigt
|
||||
}
|
||||
|
||||
// Berechnet die gesetzlich erforderliche Mindestpause basierend auf der Arbeitszeit
|
||||
|
||||
@@ -5,6 +5,40 @@ const { db } = require('../database');
|
||||
const LDAPService = require('../services/ldap-service');
|
||||
const { getDefaultRole } = require('../helpers/utils');
|
||||
|
||||
// Helper-Funktion für UTF-8 Debug-Logging
|
||||
function logUsernameEncoding(label, username) {
|
||||
if (!username) {
|
||||
console.log(`[${label}] Username is null or undefined`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.group(`🔍 ${label} - Server Console`);
|
||||
console.log('Original String:', username);
|
||||
console.log('String Length:', username.length);
|
||||
console.log('Type:', typeof username);
|
||||
|
||||
// UTF-8 Byte-Repräsentation
|
||||
const utf8Bytes = Buffer.from(username, 'utf8');
|
||||
console.log('UTF-8 Bytes:', Array.from(utf8Bytes));
|
||||
console.log('UTF-8 Bytes (Hex):', Array.from(utf8Bytes).map(b => '0x' + b.toString(16).padStart(2, '0')).join(' '));
|
||||
|
||||
// Einzelne Zeichen analysieren
|
||||
console.log('=== Character Analysis ===');
|
||||
for (let i = 0; i < username.length; i++) {
|
||||
const char = username[i];
|
||||
const codePoint = char.codePointAt(0);
|
||||
const utf8BytesForChar = Buffer.from(char, 'utf8');
|
||||
console.log(`Position ${i}: "${char}" | CodePoint: U+${codePoint.toString(16).toUpperCase().padStart(4, '0')} (${codePoint}) | UTF-8 Bytes: [${Array.from(utf8BytesForChar).join(', ')}]`);
|
||||
}
|
||||
|
||||
// URL-Encoding Test
|
||||
console.log('=== URL Encoding Test ===');
|
||||
console.log('encodeURIComponent(username):', encodeURIComponent(username));
|
||||
console.log('encodeURI(username):', encodeURI(username));
|
||||
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
// Helper-Funktion für erfolgreiche Anmeldung
|
||||
function handleSuccessfulLogin(req, res, user, rememberMe = false) {
|
||||
// Rollen als JSON-Array parsen
|
||||
@@ -66,6 +100,12 @@ function registerAuthRoutes(app) {
|
||||
const { username, password, remember_me } = req.body;
|
||||
const rememberMe = remember_me === 'on' || remember_me === true;
|
||||
|
||||
// Debug-Logging: Empfangener Username vom Client
|
||||
console.log('\n========== LOGIN REQUEST RECEIVED ==========');
|
||||
logUsernameEncoding('Username received from client', username);
|
||||
console.log('Request headers content-type:', req.headers['content-type']);
|
||||
console.log('Request body keys:', Object.keys(req.body));
|
||||
|
||||
// Prüfe ob LDAP aktiviert ist
|
||||
LDAPService.getConfig((err, ldapConfig) => {
|
||||
if (err) {
|
||||
@@ -73,11 +113,20 @@ function registerAuthRoutes(app) {
|
||||
}
|
||||
|
||||
const isLDAPEnabled = ldapConfig && ldapConfig.enabled === 1;
|
||||
console.log('LDAP enabled:', isLDAPEnabled);
|
||||
|
||||
// Wenn LDAP aktiviert ist, authentifiziere gegen LDAP
|
||||
if (isLDAPEnabled) {
|
||||
console.log('Starting LDAP authentication...');
|
||||
LDAPService.authenticate(username, password, (authErr, authSuccess, ldapUserInfo) => {
|
||||
console.log('\n========== LDAP AUTHENTICATION RESULT ==========');
|
||||
console.log('authErr:', authErr ? authErr.message : null);
|
||||
console.log('authSuccess:', authSuccess);
|
||||
console.log('ldapUserInfo:', ldapUserInfo);
|
||||
|
||||
if (authErr || !authSuccess) {
|
||||
console.log('LDAP authentication failed, trying local database fallback...');
|
||||
logUsernameEncoding('Username for DB fallback lookup', username);
|
||||
// LDAP-Authentifizierung fehlgeschlagen - prüfe lokale Datenbank als Fallback
|
||||
db.get('SELECT * FROM users WHERE username = ? COLLATE NOCASE', [username], (err, user) => {
|
||||
if (err || !user) {
|
||||
@@ -94,6 +143,10 @@ function registerAuthRoutes(app) {
|
||||
// LDAP-Authentifizierung erfolgreich - Benutzer anhand des kanonischen LDAP-Benutzernamens aus der DB holen
|
||||
// (Sync speichert den exakten LDAP-Wert, z. B. "geißlerj" oder "GeisslerJ")
|
||||
const dbLookupUsername = (ldapUserInfo && ldapUserInfo.username) ? ldapUserInfo.username : username;
|
||||
console.log('LDAP authentication successful!');
|
||||
console.log('Original username:', username);
|
||||
console.log('Canonical username from LDAP:', dbLookupUsername);
|
||||
logUsernameEncoding('Canonical username for DB lookup', dbLookupUsername);
|
||||
db.get('SELECT * FROM users WHERE username = ? COLLATE NOCASE', [dbLookupUsername], (err, user) => {
|
||||
if (err || !user) {
|
||||
return res.render('login', { error: 'Benutzer nicht in der Datenbank gefunden. Bitte führen Sie eine LDAP-Synchronisation durch.' });
|
||||
|
||||
14
server.js
14
server.js
@@ -9,8 +9,18 @@ const app = express();
|
||||
const PORT = 3333;
|
||||
|
||||
// Middleware
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(bodyParser.json());
|
||||
// UTF-8 explizit für URL-encoded Daten setzen (wichtig für Sonderzeichen wie ß, ü, ö, ä)
|
||||
app.use(bodyParser.urlencoded({
|
||||
extended: true,
|
||||
type: 'application/x-www-form-urlencoded',
|
||||
parameterLimit: 1000,
|
||||
limit: '10mb'
|
||||
}));
|
||||
// Explizit UTF-8 für JSON
|
||||
app.use(bodyParser.json({
|
||||
type: 'application/json',
|
||||
limit: '10mb'
|
||||
}));
|
||||
// Trust proxy für korrekte Client-IP-Erkennung (wichtig bei Proxies/Reverse Proxies)
|
||||
app.set('trust proxy', true);
|
||||
app.use(express.static('public'));
|
||||
|
||||
@@ -125,20 +125,35 @@ class LDAPService {
|
||||
* LDAP-Filter unterstützen UTF-8 direkt nach RFC 4515.
|
||||
* Nur die speziellen LDAP-Filter-Zeichen werden escaped.
|
||||
*/
|
||||
static escapeLDAPFilter(value) {
|
||||
static escapeLDAPFilter(value, debugLabel = '') {
|
||||
if (!value) return '';
|
||||
|
||||
// Stelle sicher, dass der Wert als String behandelt wird
|
||||
const str = String(value);
|
||||
|
||||
// Debug-Logging
|
||||
if (debugLabel) {
|
||||
console.log(`[escapeLDAPFilter ${debugLabel}] Input:`, str);
|
||||
const utf8Bytes = Buffer.from(str, 'utf8');
|
||||
console.log(`[escapeLDAPFilter ${debugLabel}] UTF-8 Bytes:`, Array.from(utf8Bytes));
|
||||
}
|
||||
|
||||
// Escape nur die speziellen LDAP-Filter-Zeichen
|
||||
// UTF-8-Zeichen wie ß, ä, ö, ü werden direkt verwendet
|
||||
return str
|
||||
const escaped = str
|
||||
.replace(/\\/g, '\\5c') // Backslash
|
||||
.replace(/\*/g, '\\2a') // Stern
|
||||
.replace(/\(/g, '\\28') // Öffnende Klammer
|
||||
.replace(/\)/g, '\\29') // Schließende Klammer
|
||||
.replace(/\0/g, '\\00'); // Null-Byte
|
||||
|
||||
if (debugLabel) {
|
||||
console.log(`[escapeLDAPFilter ${debugLabel}] Output:`, escaped);
|
||||
const escapedBytes = Buffer.from(escaped, 'utf8');
|
||||
console.log(`[escapeLDAPFilter ${debugLabel}] Escaped UTF-8 Bytes:`, Array.from(escapedBytes));
|
||||
}
|
||||
|
||||
return escaped;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,6 +287,22 @@ class LDAPService {
|
||||
// Stelle sicher, dass Username als String behandelt wird (UTF-8 wird korrekt unterstützt)
|
||||
const usernameStr = String(username || '').trim();
|
||||
|
||||
console.log('\n========== LDAP AUTHENTICATE - START ==========');
|
||||
console.log('[LDAP] Username input:', usernameStr);
|
||||
const utf8Bytes = Buffer.from(usernameStr, 'utf8');
|
||||
console.log('[LDAP] Username UTF-8 Bytes:', Array.from(utf8Bytes));
|
||||
console.log('[LDAP] Username UTF-8 Bytes (Hex):', Array.from(utf8Bytes).map(b => '0x' + b.toString(16).padStart(2, '0')).join(' '));
|
||||
console.log('[LDAP] Username length:', usernameStr.length);
|
||||
|
||||
// Character-by-character analysis
|
||||
console.log('[LDAP] Character analysis:');
|
||||
for (let i = 0; i < usernameStr.length; i++) {
|
||||
const char = usernameStr[i];
|
||||
const codePoint = char.codePointAt(0);
|
||||
const charBytes = Buffer.from(char, 'utf8');
|
||||
console.log(` [${i}] "${char}" | U+${codePoint.toString(16).toUpperCase().padStart(4, '0')} (${codePoint}) | Bytes: [${Array.from(charBytes).join(', ')}]`);
|
||||
}
|
||||
|
||||
if (!usernameStr) {
|
||||
return callback(new Error('Benutzername darf nicht leer sein'), false);
|
||||
}
|
||||
@@ -282,18 +313,115 @@ class LDAPService {
|
||||
return callback(new Error('LDAP ist nicht aktiviert'), false);
|
||||
}
|
||||
|
||||
console.log('[LDAP] Config loaded - baseDN:', config.base_dn);
|
||||
console.log('[LDAP] Config - usernameAttr:', config.username_attribute || 'sAMAccountName');
|
||||
|
||||
// LDAP-Verbindung herstellen (mit Service-Account)
|
||||
this.connect(config, (err, client) => {
|
||||
if (err) {
|
||||
console.error('[LDAP] Connection error:', err);
|
||||
return callback(err, false);
|
||||
}
|
||||
|
||||
console.log('[LDAP] Connected successfully');
|
||||
|
||||
const baseDN = config.base_dn || '';
|
||||
const usernameAttr = config.username_attribute || 'sAMAccountName';
|
||||
// OR-Filter mit mehreren Varianten (exakt, lowercase, ß/ss), damit Login trotz unterschiedlicher Schreibweise funktioniert
|
||||
|
||||
// Primär: Exakte Übereinstimmung mit sAMAccountName (wie in AD gespeichert)
|
||||
// Varianten werden als Fallback verwendet, falls exakte Suche fehlschlägt
|
||||
console.log('[LDAP] Using exact sAMAccountName match (as stored in AD)');
|
||||
const exactEscaped = this.escapeLDAPFilter(usernameStr, 'exact');
|
||||
const exactFilter = `(${usernameAttr}=${exactEscaped})`;
|
||||
|
||||
console.log('[LDAP] Exact LDAP Search Filter:', exactFilter);
|
||||
console.log('[LDAP] Exact Filter UTF-8 Bytes:', Array.from(Buffer.from(exactFilter, 'utf8')));
|
||||
|
||||
const exactSearchOptions = {
|
||||
filter: exactFilter,
|
||||
scope: 'sub',
|
||||
attributes: ['dn', usernameAttr]
|
||||
};
|
||||
|
||||
console.log('[LDAP] Starting LDAP search with exact match...');
|
||||
console.log('[LDAP] Search options:', JSON.stringify(exactSearchOptions, null, 2));
|
||||
|
||||
let userDN = null;
|
||||
let canonicalUsername = null;
|
||||
let searchAttempted = false;
|
||||
|
||||
// Zuerst exakte Suche versuchen
|
||||
client.search(baseDN, exactSearchOptions, (err, res) => {
|
||||
if (err) {
|
||||
console.error('[LDAP] Exact search error:', err);
|
||||
// Bei Fehler: Varianten als Fallback versuchen
|
||||
return this.tryVariantSearch(client, baseDN, usernameAttr, usernameStr, password, config, callback);
|
||||
}
|
||||
|
||||
searchAttempted = true;
|
||||
|
||||
res.on('searchEntry', (entry) => {
|
||||
userDN = entry.dn.toString();
|
||||
canonicalUsername = this.getAttributeValue(entry, usernameAttr) || usernameStr;
|
||||
|
||||
console.log('[LDAP] Exact search entry found!');
|
||||
console.log('[LDAP] userDN:', userDN);
|
||||
console.log('[LDAP] canonicalUsername:', canonicalUsername);
|
||||
const canonicalBytes = Buffer.from(canonicalUsername, 'utf8');
|
||||
console.log('[LDAP] canonicalUsername UTF-8 Bytes:', Array.from(canonicalBytes));
|
||||
console.log('[LDAP] canonicalUsername UTF-8 Bytes (Hex):', Array.from(canonicalBytes).map(b => '0x' + b.toString(16).padStart(2, '0')).join(' '));
|
||||
});
|
||||
|
||||
res.on('error', (err) => {
|
||||
const errorMsg = err.message || String(err);
|
||||
console.error('[LDAP] Exact search error:', errorMsg);
|
||||
console.error('[LDAP] Error details:', err);
|
||||
// Bei Fehler: Varianten als Fallback versuchen
|
||||
if (!userDN) {
|
||||
return this.tryVariantSearch(client, baseDN, usernameAttr, usernameStr, password, config, callback);
|
||||
}
|
||||
client.unbind();
|
||||
callback(new Error(`LDAP-Suchfehler: ${errorMsg}`), false);
|
||||
});
|
||||
|
||||
res.on('end', (result) => {
|
||||
console.log('[LDAP] Exact search ended. Status:', result ? result.status : 'unknown');
|
||||
console.log('[LDAP] userDN found:', userDN ? 'YES' : 'NO');
|
||||
|
||||
if (!userDN) {
|
||||
console.log('[LDAP] Exact match not found, trying variants as fallback...');
|
||||
// Exakte Suche fehlgeschlagen, Varianten als Fallback versuchen
|
||||
return this.tryVariantSearch(client, baseDN, usernameAttr, usernameStr, password, config, callback);
|
||||
}
|
||||
|
||||
// Exakte Suche erfolgreich - mit Bind fortfahren
|
||||
this.performBind(config, userDN, password, canonicalUsername, callback);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Varianten-Suche als Fallback (wenn exakte Suche fehlschlägt)
|
||||
*/
|
||||
static tryVariantSearch(client, baseDN, usernameAttr, usernameStr, password, config, callback) {
|
||||
console.log('[LDAP] Starting variant search as fallback...');
|
||||
|
||||
// OR-Filter mit mehreren Varianten (lowercase, ß/ss), damit Login trotz unterschiedlicher Schreibweise funktioniert
|
||||
const variants = this.getUsernameSearchVariants(usernameStr);
|
||||
const filterParts = variants.map(v => `(${usernameAttr}=${this.escapeLDAPFilter(v)})`);
|
||||
console.log('[LDAP] Username variants generated:', variants);
|
||||
console.log('[LDAP] Number of variants:', variants.length);
|
||||
|
||||
const filterParts = variants.map((v, idx) => {
|
||||
const escaped = this.escapeLDAPFilter(v, `variant-${idx}`);
|
||||
return `(${usernameAttr}=${escaped})`;
|
||||
});
|
||||
const searchFilter = filterParts.length === 1 ? filterParts[0] : `(|${filterParts.join('')})`;
|
||||
|
||||
console.log('[LDAP] Variant LDAP Search Filter:', searchFilter);
|
||||
console.log('[LDAP] Variant Filter UTF-8 Bytes:', Array.from(Buffer.from(searchFilter, 'utf8')));
|
||||
|
||||
const searchOptions = {
|
||||
filter: searchFilter,
|
||||
scope: 'sub',
|
||||
@@ -314,21 +442,49 @@ class LDAPService {
|
||||
userDN = entry.dn.toString();
|
||||
// Kanonischen Benutzernamen aus LDAP verwenden (für DB-Lookup nach Sync)
|
||||
canonicalUsername = this.getAttributeValue(entry, usernameAttr) || usernameStr;
|
||||
|
||||
console.log('[LDAP] Search entry found!');
|
||||
console.log('[LDAP] userDN:', userDN);
|
||||
console.log('[LDAP] canonicalUsername:', canonicalUsername);
|
||||
const canonicalBytes = Buffer.from(canonicalUsername, 'utf8');
|
||||
console.log('[LDAP] canonicalUsername UTF-8 Bytes:', Array.from(canonicalBytes));
|
||||
console.log('[LDAP] canonicalUsername UTF-8 Bytes (Hex):', Array.from(canonicalBytes).map(b => '0x' + b.toString(16).padStart(2, '0')).join(' '));
|
||||
});
|
||||
|
||||
res.on('error', (err) => {
|
||||
client.unbind();
|
||||
const errorMsg = err.message || String(err);
|
||||
console.error('[LDAP] Search error:', errorMsg);
|
||||
console.error('[LDAP] Error details:', err);
|
||||
callback(new Error(`LDAP-Suchfehler: ${errorMsg}`), false);
|
||||
});
|
||||
|
||||
res.on('end', (result) => {
|
||||
client.unbind();
|
||||
|
||||
console.log('[LDAP] Search ended. Status:', result ? result.status : 'unknown');
|
||||
console.log('[LDAP] userDN found:', userDN ? 'YES' : 'NO');
|
||||
console.log('[LDAP] canonicalUsername:', canonicalUsername);
|
||||
|
||||
if (!userDN) {
|
||||
console.error('[LDAP] User not found in LDAP (even with variants)!');
|
||||
console.error('[LDAP] Searched for:', usernameStr);
|
||||
console.error('[LDAP] Used filter:', searchFilter);
|
||||
return callback(new Error(`Benutzer "${usernameStr}" nicht gefunden. Hinweis: Prüfen Sie, ob der Benutzername korrekt ist und UTF-8-Zeichen (wie ß, ä, ö, ü) korrekt geschrieben sind.`), false);
|
||||
}
|
||||
|
||||
// Varianten-Suche erfolgreich - mit Bind fortfahren
|
||||
this.performBind(config, userDN, password, canonicalUsername, callback);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* LDAP Bind durchführen (Passwort-Authentifizierung)
|
||||
*/
|
||||
static performBind(config, userDN, password, canonicalUsername, callback) {
|
||||
console.log('[LDAP] Attempting bind with userDN:', userDN);
|
||||
|
||||
const authClient = ldap.createClient({
|
||||
url: config.url,
|
||||
timeout: 10000,
|
||||
@@ -337,6 +493,7 @@ class LDAPService {
|
||||
|
||||
authClient.on('error', (err) => {
|
||||
authClient.unbind();
|
||||
console.error('[LDAP] Bind client error:', err);
|
||||
callback(err, false);
|
||||
});
|
||||
|
||||
@@ -344,15 +501,16 @@ class LDAPService {
|
||||
authClient.unbind();
|
||||
if (err) {
|
||||
const errorMsg = err.message || String(err);
|
||||
console.error('[LDAP] Bind failed:', errorMsg);
|
||||
console.error('[LDAP] Bind error details:', err);
|
||||
return callback(new Error(`Ungültiges Passwort oder Authentifizierungsfehler: ${errorMsg}`), false);
|
||||
}
|
||||
// Erfolg: kanonischen Benutzernamen mitgeben, damit die DB-Lookup mit dem Sync-Benutzernamen funktioniert
|
||||
console.log('[LDAP] Bind successful!');
|
||||
console.log('[LDAP] Returning canonicalUsername:', canonicalUsername);
|
||||
console.log('========== LDAP AUTHENTICATE - SUCCESS ==========\n');
|
||||
callback(null, true, { username: canonicalUsername });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
<th>Rolle</th>
|
||||
<th>Personalnummer</th>
|
||||
<th>Wochenstunden</th>
|
||||
<th>Arbeitstage</th>
|
||||
<th>Arbeitstage pro Woche</th>
|
||||
<th>Urlaubstage</th>
|
||||
<th>Erstellt am</th>
|
||||
<th>Aktionen</th>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<div class="error-message"><%= error %></div>
|
||||
<% } %>
|
||||
|
||||
<form method="POST" action="/login">
|
||||
<form method="POST" action="/login" accept-charset="UTF-8" id="loginForm">
|
||||
<div class="form-group">
|
||||
<label for="username">Benutzername</label>
|
||||
<input type="text" id="username" name="username" required autofocus>
|
||||
@@ -40,6 +40,56 @@
|
||||
|
||||
<button type="submit" class="btn btn-primary">Anmelden</button>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
// Debug-Logging für LDAP-Authentifizierung mit UTF-8-Zeichen
|
||||
document.getElementById('loginForm').addEventListener('submit', function(e) {
|
||||
const usernameInput = document.getElementById('username');
|
||||
const username = usernameInput.value;
|
||||
|
||||
console.group('🔍 LDAP Login Debug - Browser Console');
|
||||
console.log('=== Username Analysis ===');
|
||||
console.log('Original Input:', username);
|
||||
console.log('String Length:', username.length);
|
||||
console.log('Type:', typeof username);
|
||||
|
||||
// UTF-8 Byte-Repräsentation
|
||||
const encoder = new TextEncoder();
|
||||
const utf8Bytes = encoder.encode(username);
|
||||
console.log('UTF-8 Bytes:', Array.from(utf8Bytes));
|
||||
console.log('UTF-8 Bytes (Hex):', Array.from(utf8Bytes).map(b => '0x' + b.toString(16).padStart(2, '0')).join(' '));
|
||||
|
||||
// Einzelne Zeichen analysieren
|
||||
console.log('=== Character Analysis ===');
|
||||
for (let i = 0; i < username.length; i++) {
|
||||
const char = username[i];
|
||||
const codePoint = char.codePointAt(0);
|
||||
const utf8BytesForChar = encoder.encode(char);
|
||||
console.log(`Position ${i}: "${char}" | CodePoint: U+${codePoint.toString(16).toUpperCase().padStart(4, '0')} (${codePoint}) | UTF-8 Bytes: [${Array.from(utf8BytesForChar).join(', ')}]`);
|
||||
}
|
||||
|
||||
// Form-Daten, die gesendet werden
|
||||
console.log('=== Form Data ===');
|
||||
const formData = new FormData(this);
|
||||
const formDataObj = {};
|
||||
for (let [key, value] of formData.entries()) {
|
||||
if (key === 'password') {
|
||||
formDataObj[key] = '***hidden***';
|
||||
} else {
|
||||
formDataObj[key] = value;
|
||||
}
|
||||
}
|
||||
console.log('Form Data Object:', formDataObj);
|
||||
console.log('Username in FormData:', formData.get('username'));
|
||||
|
||||
// URL-Encoding Test
|
||||
console.log('=== URL Encoding Test ===');
|
||||
console.log('encodeURIComponent(username):', encodeURIComponent(username));
|
||||
console.log('encodeURI(username):', encodeURI(username));
|
||||
|
||||
console.groupEnd();
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user