auth/ldap/AsyncLdapConnection.js

/**
 * @module flitter-auth/ldap/AsyncLdapConnection
 */

const ldap = require('ldap')
const ssha = require('ssha')

/**
 * Asynchronous helper methods for the jdap.js library.
 * @class
 */
class AsyncLdapConnection {
    constructor(connection){
        this.connection = connection
    }

    /**
     * Bind the active connection to the specified DN with the password.
     * @param {string} dn - the fully-qualified bind DN
     * @param {string} password - the password to authenticate against
     * @param {Array<ldap/Control>} controls - collection of controls to pass to ldap.js
     * @returns {Promise<module:flitter-auth/ldap/AsyncLdapConnection~AsyncLdapConnection>} - resolves to this if successful
     */
    bind(dn, password, controls){
        return new Promise((resolve, reject) => {
            this.connection.bind(dn, password, controls, (err) => {
                if ( err ) reject(err)
                else resolve(this)
            })
        })
    }

    /**
     * Search for all objects in the specified base, covered by the filter, for the specified attributes.
     * @param {string} base - fully-qualified base to search
     * @param {string} filter - LDAP-compliant string to filter items
     * @param {Array<string>} attributes - object attributes to fetch
     * @returns {Promise<Array<object>>} - collection of matches
     */
    search(base, filter, attributes){
        return new Promise(((resolve, reject) => {
            this.connection.search(base, {
                scope: 'sub',
                attributes,
                filter,
            }, (err, response) => {
                if ( err ) reject (err)
                else {
                    const entries = []
                    response.on('searchEntry', (en) => entries.push(en.object))
                    response.on('error', (e) => reject(e))
                    response.on('end', (r) => resolve(entries))
                }
            })
        }))
    }

    /**
     * Check if the specified attribute is equal to the value for the object with the fully-qualified object DN.
     * @param {string} object - fully-qualified object DN
     * @param {string} attribute - attribute to compare
     * @param {*} value - value to match
     * @returns {Promise<boolean>} - resolves true if the attribute matches the value
     */
    compare(object, attribute, value){
        return new Promise(((resolve, reject) => {
            this.connection.compare(object, attribute, value, (err, matched) => {
                if ( err ) reject(err)
                else resolve(!!matched)
            })
        }))
    }

    /**
     * Check if the specified value can authenticate the user with the fully-qualified DN.
     * @param {string} object - fully-qualified DN of the user to bind
     * @param {string} password - password to check
     * @returns {Promise<boolean>} - true if authentication succeeds
     */
    password_check(object, password){
        return new Promise(resolve => {
            this.connection.bind(object, password, [], (err) => {
                resolve(!err)
            })
        })
    }

    /**
     * Reset the userPassword of the specified bind object by SSHA hashing the specified password.
     * @param {string} object - fully-qualified DN of the user to reset
     * @param {string} password - the password to set
     * @returns {Promise<void>}
     */
    password_reset(object, password){
        const change = new ldap.Change({
            operation: 'replace',
            modification: {
                userPassword: ssha.create(password),
            }
        })

        return new Promise((resolve, reject) => {
            this.connection.modify(object, change, err => {
                if ( err ) reject(err)
                else resolve()
            })
        })
    }

    /**
     * Modify the values of the specified object.
     * @param {string} object - fully qualified DN of the object to be modified
     * @param {object} entry - collection of key-value pairs to set on the object
     * @returns {Promise<void>}
     */
    modify(object, entry){
        const change = new ldap.Change({
            operation: 'replace',
            modification: entry,
        })

        return new Promise((resolve, reject) => {
            this.connection.modify(object, change, err => {
                if ( err ) reject(err)
                else resolve()
            })
        })
    }

    /**
     * Similar to modify, add the following attributes to the specified object. If they don't exist,
     * the attributes will be created. If they do, the specified values will be appended to the existing
     * attributes.
     * @param {string} object - fully qualified DN of the object to be modified
     * @param {object} entry - collection of key-value pairs to be set on the object
     * @returns {Promise<void>}
     */
    add_to(object, entry){
        const change = new ldap.Change({
            operation: 'add',
            modification: entry,
        })

        return new Promise((resolve, reject) => {
            this.connection.modify(object, change, err => {
                if ( err ) reject(err)
                else resolve()
            })
        })
    }

    /**
     * Create the object with the specified DN with the specified attributes.
     * @param {string} object - fully-qualified DN of the object to be created
     * @param {object} entry - collection of attribute-value pairs for the object
     * @returns {Promise<void>}
     */
    add(object, entry){
        return new Promise((resolve, reject) => {
            this.connection.add(object, entry, (err) => {
                if ( err ) reject(err)
                else resolve()
            })
        })
    }

    /**
     * Delete the object with the specified DN.
     * @param {string} object - fully-qualified DN of the object to be deleted
     * @returns {Promise<void>}
     */
    delete(object){
        return new Promise((resolve, reject) => {
            this.connection.del(object, (err) => {
                if ( err ) reject(err)
                else resolve()
            })
        })
    }
}

module.exports = exports = AsyncLdapConnection