/* eslint-disable @typescript-eslint/no-unused-vars */ import { objectClone, objectLoop, objectMap, objectRemap } from '@dzeio/object-util' import ldap from 'ldapjs' import type DaoAdapter from 'models/DaoAdapter' import type { DBPull } from 'models/DaoAdapter' import { type Query } from 'models/Query' import Schema, { type Implementation, type Model } from 'models/Schema' type LDAPFields = 'uid' | 'mail' | 'givenname' | 'sn' | 'jpegPhoto' | 'password' export default class LDAPAdapter implements DaoAdapter { private reverseReference: Partial> = {} private attributes: Array = [] public constructor( public readonly schema: Schema, public readonly options: { url: string dnSuffix: string adminUsername: string adminPassword: string fieldsCorrespondance?: Partial> } ) { objectLoop(options.fieldsCorrespondance ?? {}, (value, key) => { this.reverseReference[value] = key this.attributes.push(value) }) } // TODO: make it clearer what it does public async create(_obj: Partial>): Promise | null> { throw new Error('not implemented') } // eslint-disable-next-line complexity public async read(query?: Query> | undefined): Promise> { const passwordField = this.options.fieldsCorrespondance?.password ?? 'password' const doLogin = !!query?.[passwordField] const emptyResult = { rows: 0, rowsTotal: 0, page: 1, pageTotal: 0, data: [] } if (!query) { return emptyResult } // console.log(await this.ldapFind({mail: 'f.bouillon@aptatio.com'})) const userdn = objectMap(query, (value, key) => `${(this.options.fieldsCorrespondance as any)[key] ?? key}=${value}`) ?.filter((it) => it.slice(0, it.indexOf('=')) !== passwordField) ?.join(',') if (!doLogin) { const client = await this.bind(`cn=${this.options.adminUsername},${this.options.dnSuffix}`, this.options.adminPassword) // @ts-expect-error nique ta mere // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const results = (await this.ldapFind(client, objectMap(query, (value, key) => [key as string, '', value as string]) .map((it) => ({key: it[0] as LDAPFields, value: it[2]}))! )).map((it) => this.schema.parse( objectRemap(it, (value, key) => ({key: this.reverseReference[key as string] as string, value: value})) )).filter((it): it is Implementation => !!it) return { rows: results.length, rowsTotal: results.length, page: 1, pageTotal: 1, data: results } } try { const clone = objectClone(query) delete clone.password const res = await this.read(clone) const user = res.data[0] if (!user) { return emptyResult } const password = query.password as string ?? '' const client = await this.bind(`cn=${user[this.reverseReference.uid as keyof typeof user]!},${this.options.dnSuffix}`, password) // @ts-expect-error nique x2 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const results = (await this.ldapFind(client, objectMap(clone, (value, key) => ({key: key as keyof LDAPFields, value: value})) )).map((it) => this.schema.parse( objectRemap(it, (value, key) => ({key: this.reverseReference[key as string] as string, value: value})) )).filter((it): it is Implementation => !!it) if (results.length !== 1) { return emptyResult } return { rows: results.length, rowsTotal: results.length, page: 1, pageTotal: 1, data: results } } catch (e) { console.log('error, user not found', e) return emptyResult } } public async update(_obj: Partial>): Promise | null> { throw new Error('not implemented') } public async patch(_id: string, _obj: Partial>): Promise | null> { throw new Error('not implemented') } public async delete(_obj: Partial>): Promise { throw new Error('not implemented') } private bind(dn: string, password: string): Promise { const client = ldap.createClient({ url: this.options.url }) return new Promise((res, rej) => { client.on('connect', () => { client.bind(dn, password, (err) => { if (err) { console.error('error binding as', dn, err) client.unbind() return } console.log('binded as', dn) res(client) }) }) .on('timeout', (err) => rej(err)) .on('connectTimeout', (err) => rej(err)) .on('error', (err) => rej(err)) .on('connectError', (err) => rej(err)) }) } private async ldapFind(client: ldap.Client, filters: Array<{key: LDAPFields, value: string}>): Promise | undefined>>> { if (filters.length === 0) { return [] } const firstFilter = filters.shift()! return new Promise | undefined>>>((res, rej) => { const users: Array | undefined>> = [] client.search( this.options.dnSuffix, { filter: new ldap.EqualityFilter({ attribute: firstFilter.key as any, value: firstFilter.value, }), scope: 'sub', attributes: this.attributes }, (err, search) => { if (err) { rej(err) } // console.log('search', search, err) search.on('searchEntry', (entry) => { users.push(this.parseUser(entry)) }).on('error', (err2) => { rej(err2) client.unbind() console.error('error in search lol', err2) }).on('end', () => { console.log(users) res(users) client.unbind() }) } ) }) } private parseUser(usr: ldap.SearchEntry): Record | undefined> { const user: Record | undefined> = { dn: usr.objectName ?? undefined } usr.attributes.forEach((attribute) => { user[attribute.type] = attribute.values.length === 1 ? attribute.values[0] : attribute.values }) return user } }