Files
SDSStundenerfassung/ldap-service.js
Carsten Graf 17838c4f1e FirstCommit
2026-01-22 01:13:28 +01:00

280 lines
8.0 KiB
JavaScript

const ldap = require('ldapjs');
const { db } = require('./database');
const bcrypt = require('bcryptjs');
/**
* LDAP-Service für Benutzer-Synchronisation
*/
class LDAPService {
/**
* LDAP-Konfiguration aus der Datenbank abrufen
*/
static getConfig(callback) {
db.get('SELECT * FROM ldap_config WHERE id = 1', (err, config) => {
if (err) {
return callback(err, null);
}
callback(null, config);
});
}
/**
* LDAP-Verbindung herstellen
*/
static connect(config, callback) {
if (!config || !config.enabled || !config.url) {
return callback(new Error('LDAP ist nicht konfiguriert oder deaktiviert'));
}
const client = ldap.createClient({
url: config.url,
timeout: 10000,
connectTimeout: 10000
});
// Fehlerbehandlung
client.on('error', (err) => {
callback(err, null);
});
// Bind mit Credentials
const bindDN = config.bind_dn || '';
const bindPassword = config.bind_password || '';
// Hinweis: Passwort wird im Klartext gespeichert
// In einer produktiven Umgebung sollte man eine Verschlüsselung mit einem Master-Key verwenden
client.bind(bindDN, bindPassword, (err) => {
if (err) {
client.unbind();
return callback(err, null);
}
callback(null, client);
});
}
/**
* Benutzer aus LDAP abrufen
*/
static searchUsers(client, config, callback) {
const baseDN = config.base_dn || '';
const searchFilter = config.user_search_filter || '(objectClass=person)';
const searchOptions = {
filter: searchFilter,
scope: 'sub',
attributes: [
config.username_attribute || 'cn',
config.firstname_attribute || 'givenName',
config.lastname_attribute || 'sn'
]
};
const users = [];
client.search(baseDN, searchOptions, (err, res) => {
if (err) {
return callback(err, null);
}
res.on('searchEntry', (entry) => {
const user = {
username: this.getAttributeValue(entry, config.username_attribute || 'cn'),
firstname: this.getAttributeValue(entry, config.firstname_attribute || 'givenName'),
lastname: this.getAttributeValue(entry, config.lastname_attribute || 'sn')
};
// Nur Benutzer mit allen erforderlichen Feldern hinzufügen
if (user.username && user.firstname && user.lastname) {
users.push(user);
}
});
res.on('error', (err) => {
callback(err, null);
});
res.on('end', (result) => {
if (result && result.status !== 0) {
return callback(new Error(`LDAP-Suche fehlgeschlagen: ${result.status}`), null);
}
callback(null, users);
});
});
}
/**
* Wert eines LDAP-Attributs extrahieren
*/
static getAttributeValue(entry, attributeName) {
const attr = entry.attributes.find(a => a.type === attributeName);
if (!attr) {
return null;
}
return Array.isArray(attr.values) ? attr.values[0] : attr.values;
}
/**
* Benutzer in SQLite synchronisieren
*/
static syncUsers(ldapUsers, callback) {
let syncedCount = 0;
let errorCount = 0;
const errors = [];
if (!ldapUsers || ldapUsers.length === 0) {
return callback(null, { synced: 0, errors: [] });
}
// Verarbeite jeden Benutzer
const processUser = (index) => {
if (index >= ldapUsers.length) {
return callback(null, { synced: syncedCount, errors: errors });
}
const ldapUser = ldapUsers[index];
const username = ldapUser.username.trim();
const firstname = ldapUser.firstname.trim();
const lastname = ldapUser.lastname.trim();
// Prüfe ob Benutzer bereits existiert
db.get('SELECT id, role FROM users WHERE username = ?', [username], (err, existingUser) => {
if (err) {
errors.push(`Fehler beim Prüfen von ${username}: ${err.message}`);
errorCount++;
return processUser(index + 1);
}
if (existingUser) {
// Benutzer existiert - aktualisiere nur Name, behalte Rolle
db.run(
'UPDATE users SET firstname = ?, lastname = ? WHERE username = ?',
[firstname, lastname, username],
(err) => {
if (err) {
errors.push(`Fehler beim Aktualisieren von ${username}: ${err.message}`);
errorCount++;
} else {
syncedCount++;
}
processUser(index + 1);
}
);
} else {
// Neuer Benutzer - erstelle mit Standard-Rolle
// Generiere ein zufälliges Passwort (Benutzer muss es beim ersten Login ändern)
const defaultPassword = bcrypt.hashSync('changeme123', 10);
db.run(
'INSERT INTO users (username, password, firstname, lastname, role) VALUES (?, ?, ?, ?, ?)',
[username, defaultPassword, firstname, lastname, 'mitarbeiter'],
(err) => {
if (err) {
errors.push(`Fehler beim Erstellen von ${username}: ${err.message}`);
errorCount++;
} else {
syncedCount++;
}
processUser(index + 1);
}
);
}
});
};
processUser(0);
}
/**
* Sync-Log-Eintrag erstellen
*/
static createSyncLog(syncType, status, usersSynced, errorMessage, callback) {
const startedAt = new Date().toISOString();
const completedAt = new Date().toISOString();
db.run(
`INSERT INTO ldap_sync_log (sync_type, status, users_synced, error_message, sync_started_at, sync_completed_at)
VALUES (?, ?, ?, ?, ?, ?)`,
[syncType, status, usersSynced, errorMessage || null, startedAt, completedAt],
(err) => {
if (callback) {
callback(err);
}
}
);
}
/**
* Letzte Synchronisation aktualisieren
*/
static updateLastSync(callback) {
db.run(
'UPDATE ldap_config SET last_sync = CURRENT_TIMESTAMP WHERE id = 1',
(err) => {
if (callback) {
callback(err);
}
}
);
}
/**
* Vollständige Synchronisation durchführen
*/
static performSync(syncType, callback) {
const startedAt = new Date();
// Konfiguration abrufen
this.getConfig((err, config) => {
if (err) {
this.createSyncLog(syncType, 'error', 0, `Fehler beim Abrufen der Konfiguration: ${err.message}`, () => {});
return callback(err);
}
if (!config || !config.enabled) {
const errorMsg = 'LDAP-Synchronisation ist nicht aktiviert';
this.createSyncLog(syncType, 'error', 0, errorMsg, () => {});
return callback(new Error(errorMsg));
}
// LDAP-Verbindung herstellen
this.connect(config, (err, client) => {
if (err) {
this.createSyncLog(syncType, 'error', 0, `LDAP-Verbindungsfehler: ${err.message}`, () => {});
return callback(err);
}
// Benutzer aus LDAP abrufen
this.searchUsers(client, config, (err, ldapUsers) => {
// Verbindung schließen
client.unbind();
if (err) {
this.createSyncLog(syncType, 'error', 0, `LDAP-Suchfehler: ${err.message}`, () => {});
return callback(err);
}
// Benutzer synchronisieren
this.syncUsers(ldapUsers, (err, result) => {
if (err) {
this.createSyncLog(syncType, 'error', result.synced, `Sync-Fehler: ${err.message}`, () => {});
return callback(err);
}
// Letzte Synchronisation aktualisieren
this.updateLastSync(() => {
const status = result.errors.length > 0 ? 'error' : 'success';
const errorMsg = result.errors.length > 0 ? result.errors.join('; ') : null;
this.createSyncLog(syncType, status, result.synced, errorMsg, () => {
callback(null, result);
});
});
});
});
});
});
}
}
module.exports = LDAPService;