ldapjs / node-ldapjs

LDAP Client and Server API for node.js
http://ldapjs.org
MIT License
1.61k stars 440 forks source link

pereodic crash #873

Closed janeblower closed 1 year ago

janeblower commented 1 year ago

Hello!

After some time running I get the following error:

node:events:489 throw er; // Unhandled 'error' event ^

Error: read ECONNRESET at TCP.onStreamRead (node:internal/stream_base_commons:217:20) Emitted 'error' event on Client instance at: at Socket.onSocketError (/mnt/f/Code/EmailVerify-main/node_modules/ldapjs/lib/client/client.js:1001:12) at Socket.emit (node:events:511:28) at emitErrorNT (node:internal/streams/destroy:151:8) at emitErrorCloseNT (node:internal/streams/destroy:116:3) at process.processTicksAndRejections (node:internal/process/task_queues:82:21) { errno: -104, code: 'ECONNRESET', syscall: 'read' }

What am I doing wrong?


import LDAP from "ldapjs";

const server = ""; //ip or hostname
const user = ""; // user@domain
const password = ""; //user password
const adSuffix = "dc=rias,dc=ru"; 

const client = LDAP.createClient({
  url: `ldap://${server}`,
});

client.bind(user, password, (err) => {
  if (err) {
    console.log("Error in new connection " + err);
  } else {
    console.log("Connection success");
  }
});

export const searchUser = (value) => {
  return new Promise((resolve, reject) => {
    const opts = {
      filter: `(mail=${value})`,
      scope: "sub",
      attributes: ["cn", "mail"],
    };

    client.search(adSuffix, opts, (err, res) => {
      if (err) {
        reject(err);
      } else {
        res.on("searchEntry", (entry) => {
          resolve(entry.pojo.attributes.find((i) => i.type === "cn")?.values[0]);
        });
        res.on("error", (err) => {
          reject(err);
        });
      }
    });
  });
};
jsumners commented 1 year ago

You have not registered an error event handler on the client.

kmilacic commented 1 year ago

I have the same problem. So I should do something like this? ldapClient.on('error', err => { console.error('error: ' + err.message); });

Is this enough? Do I have to reinstantiate my client or do something else?

jsumners commented 1 year ago

Have you tried adding the error listener? I would imagine it depends on the error, and your application, as to what needs to be done after receiving the error.

driverjb commented 1 year ago

I've had the best results by wrapping an ldapjs Client in my own LdapClient abstraction class. Internally, I create the client, and attach to it a handler that listens for the 'error' event. When the error occurs the handler re-binds the client. That way I call my searches and modifications through my wrapper class and the wrapper class takes care of the internal ldapjs class.

classmatewu commented 1 year ago

Hi, I also met this code if I not unbind or destroy the socket, I also know this question is about the RST response from ldap server. But what I'm curious about is that every time I get this error, it happens after the client has been idle for about 10-15 minutes. During this period, I didn't send any request. Why do I receive such a tcp return packet? Is it an internal heartbeat request in the ldapjs package? I tried to find the answer from the source code, but I couldn't find it, so I came here to bother. Thanks a lot. @jsumners

    const client = ldapjs.createClient({ url, connectTimeout });

    client.bind(bindDN, bindPassword, (err) => {
      if (err) {
        console.log(err)
      }
    });

    client.on('error', (err) => {
      if (err) {
        console.log(err)
      }
    });
jsumners commented 1 year ago

I also know this question is about the RST response from ldap server

What message is this? I don't recall reading about any such "RST response" in the spec.

But what I'm curious about is that every time I get this error, it happens after the client has been idle for about 10-15 minutes. During this period, I didn't send any request. Why do I receive such a tcp return packet?

This is likely a basic TCP keepalive packet.

Is it an internal heartbeat request in the ldapjs package?

I do not believe ldapjs does any such thing.

I'm closing this issue since the original reporter has decided not to respond. If a reproduction can be provided, we can reopen it.

Fray117 commented 8 months ago

I think this issue it's a bit old, but somehow I met a similar problems when I tried to write an helper (service) in NestJS.

This issue not came after every restart until it hit a certain interval.

My issue related to credentials validation to Microsoft AD which just went success whatever the password with just a correct dn.

AFAIK, other teams working with this AD were using php which only validate once and flush the client and I assumed with hanging connection like this on JS had some problems.

Minimal reproduction

First, I import Client and make an variable to containing the ldapjs client

import { Client, SearchEntry, createClient } from 'ldapjs'

export class LdapService implements OnApplicationBootstrap {
    private auth: Client
}

then I add function that run everytime the NestJS is started (or restart) to initiate the client

async onApplicationBootstrap() {
    const ldapOptions = {
        url: LDAP_URL,
        reconnect: true,
    }

    this.auth = createClient(ldapOptions)

    this.auth.on('connectError', (err) => {
        this.logger.warn(`Auth ${err}`)
    })

    this.auth.on('error', (err) => this.logger.error(`Auth ${err}`))
}

Finally I had an function to verify dn and password by bind

async authenticateLdap(dn, password): Promise <boolean> {
    return new Promise((resolve) => {
        this.auth.bind(dn, password, (err) => {
            if (err) {
                // Bind failed
                resolve(false)
            } else {
                // Bind was successful, immediately unbind for next user authentication
                this.auth.unbind()

                resolve(true)
            }
        })
    })
}

I tried to unbind immediately after it succesfully binded

However in about several minutes later it came an error Client Error: read ECONNRESET which returned by this listener

this.auth.on('error', (err) => this.logger.error(`Auth ${err}`))