LDAP search using ldapjs

I want to use ldapjs to search a Microsoft Active Directory (AD) server.

This appears to be a three step procedure. First you need to bind to the server (authenticate), then you need to search & filter the information you are looking for, and then you need to get the results out of the returned Event Emitter.

The SO answer here is very informative, and I have been able to get the desired results server side, but I want to return the searchEntry to the client instead of printing to console. How can I do that?

maybe just the usual way, set the server side in meteor.methods and use meteor.call in client side ?

@fg1 I am trying exactly that, but I have encountered problems:

im not expert enough to accurate answer, but maybe try replace meteor.call by meteor.callPromise from that package https://atmospherejs.com/deanius/promise

As I understand the problem: I want to use the Meteor.wrapAsync method server side in order to turn the asynchronous LDAP response from ldapjs synchronous. Meteor.wrapAsync returns a promise and returning that in a Meteor method will resolve that promise and send the result to the client via DPP. Unfortunately, the result contains a circular object that cannot be serialised. So I need to somehow resolve the promise server side and deal with the circular object before returning to the client.

maybe as ldapjs response is synchronous so it already return a promise and you just have to add an await before ? otherwise i’m not experienced with wrapAsync, i’m just surprised that seems an heavy solution, btw you could also read that https://blog.meteor.com/using-promises-and-async-await-in-meteor-8f6f4a04f998 :

we’ll sometimes hit the dreaded Meteor code must always run within a Fiber issue. I’m going to show you how to handle that without resorting to Meteor.wrapAsync

ldapjs response is asynchronous. And maybe I am overcomplicating stuff by using Meteor.wrapAsync, but that was proposed by @robfallows - who also authored that article.

So, to chip in with my 2 cents.

Meteor.wrapAsync does not return a Promise. It just ensures that the wrapped code runs in a Fiber. It also expects the wrapped code to have a callback with the “standard” (error, result) signature. The (asynchronous) wrapped code may then be treated like any other Meteor sync-style code,

// wrap someAsynchronousCode(parameters, (error, result) => {})
const wrappedCode = Meteor.wrapAsync(someAsynchronousCode);

try {
  const result = wrappedCode(parameters);
  // ...
} catch (error) {
  // ...
}

If you are seeing Promise-related errors, then as @fg1 suggests, maybe the ldapjs library automatically returns a Promise if a callback is not used (I’ve not used it, so can’t be certain). In that situation, use async/await. So, in a Meteor method:

Meteor.methods({
  async myMethod() {
  try {
    const result = await someFunctionReturningAPromise(...);
    return result;
  } catch (error) {
    // ...
  }
});

Hmm, I was trying to show how to not use Meteor.wrapAsync in that article :wink:

1 Like

Sorry @robfallows, me bad. It was in this post, that you suggested using Meteor.wrapAsync

I am stuck on the same problem variation. I want to lookup a particular piece of info in the LDAP directory. First I need to bind for authentication:

/**
   * Method for binding to a LDAP server.
   *
   * @param  {Object} client   ldapjs client.
   * @param  {String} user     LDAP user.
   * @param  {String} password LDAP password.
   * @throws {Error}           if bind fails.
   * @return {Boolean}         true if bind else throws.
   */
  const ldapBind = (client, user, password) => {
    const clientBind = Meteor.wrapAsync(client.bind, client);

    try {
      const res = clientBind(user, password);
      console.log('ldapBind', res); // res is a cyclic object
      return true;
    } catch (err) {
      throw new Meteor.Error('bind failed', err);
    }
  };

Next I need to conduct a search:

  /**
   * Method for searching LDAP (which is already bound).
   * @param    {Object} client     ldapjs client.
   * @param    {String} base       LDAP DN string.
   * @param    {Object} options    Search options.
   * @property {String} scope      The search scope (Default = base).
   * @property {String} filter     Search filter (Optional),
   * @property {Array}  attributes Attributes to return.
   * @throws   {Error}             if search fials.
   * @return   {Object}            Event Emitter.
   */
  const ldapSearch = (client, base, options) => {
    const clientSearch = Meteor.wrapAsync(client.search, client);

    try {
      const res = clientSearch(base, options);
      console.log('ldapSearch', res);
      return res;
    } catch (searchErr) {
      throw new Meteor.Error('search failed', searchErr);
    }
  };

And finally, I need to get the results out of the EventEmitter (where I am stuck):

  /**
   * Get the search result from a LDAP search.
   *
   * @param  {Object} search Event emitter.
   * @throws {Error}         if getting result fails.
   * @return {Object}        Object with search results.
   */
  const ldapGetResult_OLD = (search) => {
    const resultGet = Meteor.wrapAsync(search.on, search);

    try {
      const entry = resultGet('searchEntry');
      const res = entry.object;

      console.log('ldapGetResult', JSON.stringify(res));

      return res;
    } catch (getErr) {
      throw new Meteor.Error('get result failed', getErr);
    }
  };

Which results in

UnhandledPromiseRejectionWarning: TypeError: Converting circular structure to JSON
W20190107-16:45:37.678(1)? (STDERR)     at JSON.stringify (<anonymous>)

The callback signature of search.on is not (error, result), so Meteor.wrapAsync will not work as expected. Wrapping the callback in Meteor.bindEnvironment is the way to resolve that. However, it looks like something in there is using Promises, or you would not be getting an UnhandledPromiseRejectionWarning exception,

Which ldap library are you using?

This is ldapjs

I was able to get this working using https://www.npmjs.com/package/activedirectory

It uses ldapjs as well, but is a little more convenient to use. It is still required to use all the usual ‘await/async’, ‘futures’, ‘bindenvironment’ type tricks tho. I can show example code if needed, but I get that it is an old thread.