const { Op } = require('sequelize'); class IdentityManager { constructor(adManager, AuthenticationModel) { this.ad = adManager || null; this.Authentication = AuthenticationModel; } /** * ----------------------------------------------------- * REQUIRED FIELDS (MANUAL USER) * ----------------------------------------------------- */ REQUIRED_FIELDS = [ 'sAMAccountName', 'mail', 'givenName', 'sn', 'password' ]; /** * ----------------------------------------------------- * VALIDATE MANUAL USER * ----------------------------------------------------- */ validateManualUser(user) { const missing = []; for (const field of this.REQUIRED_FIELDS) { if ( user[field] === undefined || user[field] === null || user[field] === '' ) { missing.push(field); } } if (missing.length) { throw new Error( `Fehlende Pflichtfelder: ${missing.join(', ')}` ); } // 🔍 Optional: einfache Zusatzvalidierungen if (user.mail && !user.mail.includes('@')) { throw new Error('Ungültige E-Mail-Adresse'); } if (user.password && user.password.length < 6) { throw new Error('Passwort muss mindestens 6 Zeichen lang sein'); } } /** * ----------------------------------------------------- * FIXED MAPPING (AD → Authentication) * ----------------------------------------------------- */ mapAdObject(obj) { if (!obj || !obj.objectGUID) return null; return { ObjectGUID: obj.objectGUID, sAMAccountName: obj.sAMAccountName || obj.cn || null, mail: obj.mail || null, givenName: obj.givenName || null, sn: obj.sn || null, employeeID: obj.employeeID || null, title: obj.title || null, department: obj.department || null, streetAddress: obj.streetAddress || null, userAccountControl_ID: obj.userAccountControl || null, authenticationType_ID: 1, telephoneNumber: obj.telephoneNumber || null, physicalDeliveryOfficeName: obj.physicalDeliveryOfficeName || null, distinguishedName: obj.dn || null, password: null, refreshtoken: null, active: true, online: false }; } /** * ----------------------------------------------------- * DEDUP (wie SQL UNION) * ----------------------------------------------------- */ deduplicateByGUID(items) { const map = new Map(); for (const item of items) { if (!item?.ObjectGUID) continue; map.set(item.ObjectGUID, item); } return Array.from(map.values()); } /** * ----------------------------------------------------- * TABLE CHECK / CREATE * ----------------------------------------------------- */ async ensureTable() { const qi = this.Authentication.sequelize.getQueryInterface(); const tables = await qi.showAllTables(); const exists = tables.includes('Authentication'); if (!exists) { await this.Authentication.sync(); return false; } return true; } /** * ----------------------------------------------------- * CORE SYNC (INTELLIGENT) * ----------------------------------------------------- */ async syncFromAD() { if (!this.ad) { throw new Error('AD nicht konfiguriert'); } const [users, groups, computers] = await Promise.all([ this.ad.findUsers('*'), this.ad.findGroups('*'), this.ad.getComputers() ]); const mapped = this.deduplicateByGUID([ ...users.map(u => this.mapAdObject(u)), ...groups.map(g => this.mapAdObject(g)), ...computers.map(c => this.mapAdObject(c)) ].filter(Boolean)); if (!mapped.length) { return { total: 0, deactivated: 0 }; } await this.Authentication.bulkCreate(mapped, { updateOnDuplicate: [ 'mail', 'givenName', 'sn', 'employeeID', 'title', 'department', 'streetAddress', 'userAccountControl_ID', 'telephoneNumber', 'physicalDeliveryOfficeName', 'distinguishedName', 'active' ] }); const existing = await this.Authentication.findAll({ where: { authenticationType_ID: 1 }, attributes: ['ObjectGUID'] }); const adGuids = new Set(mapped.map(u => u.ObjectGUID)); const toDeactivate = existing .filter(e => !adGuids.has(e.ObjectGUID)) .map(e => e.ObjectGUID); if (toDeactivate.length) { await this.Authentication.update( { active: false }, { where: { ObjectGUID: toDeactivate } } ); } return { total: mapped.length, deactivated: toDeactivate.length, adGuids: Array.from(adGuids) }; } /** * ----------------------------------------------------- * OPTIONAL: HARD DELETE * ----------------------------------------------------- */ async removeDeletedADObjects(adGuids) { return this.Authentication.destroy({ where: { authenticationType_ID: 1, ObjectGUID: { [Op.notIn]: adGuids } } }); } /** * ----------------------------------------------------- * RECREATE * ----------------------------------------------------- */ async recreateAuthentications(hardReset = false) { let message = ''; const exists = await this.ensureTable(); if (!exists) { message = 'Tabelle wurde neu erstellt '; } try { const result = await this.syncFromAD(); message += `Sync abgeschlossen (${result.total} Objekte)`; if (result.deactivated) { message += `, ${result.deactivated} deaktiviert`; } if (hardReset) { const deleted = await this.removeDeletedADObjects(result.adGuids); message += `, ${deleted} gelöscht`; } } catch (err) { message += 'Fehler: ' + err.message; } return message; } /** * ----------------------------------------------------- * MANUAL USER (MIT VALIDATION) * ----------------------------------------------------- */ async createManualUser(user) { this.validateManualUser(user); return this.Authentication.create({ ...user, authenticationType_ID: 2, active: true, online: false }); } /** * ----------------------------------------------------- * MANUAL BULK (MIT VALIDATION) * ----------------------------------------------------- */ async createManualUsers(users) { const errors = []; users.forEach((user, index) => { try { this.validateManualUser(user); } catch (err) { errors.push(`User ${index}: ${err.message}`); } }); if (errors.length) { throw new Error(errors.join(' | ')); } return this.Authentication.bulkCreate( users.map(user => ({ ...user, authenticationType_ID: 2, active: true, online: false })) ); } /** * ----------------------------------------------------- * GET USER * ----------------------------------------------------- */ async getUser(username) { return this.Authentication.findOne({ where: { sAMAccountName: username } }); } } module.exports = IdentityManager;