Node-ldapjs: Possible bug - client.bind causing client to reload

Created on 30 Jan 2020  Â·  16Comments  Â·  Source: ldapjs/node-ldapjs

Hi!
I'm writing a page to reset user's LDAP password. I've started with a simple form asking to provide an email address. Here I'm experiencing a problem: everything is working fine if a user submits email address only once, but if the user makes a mistake and have to re-submit the corrected email address my browser refreshes the page and the client cannot receive server's response. I've debugged this step by step and found that client refreshes right after client.bind statement and doesn't cause failure, the script continues the execution.
Here is my code:

 var tlsOptions = { "ejectUnauthorized" false }
    client = ldap.createClient({
        url: ldapURL,
        tlsOptions: tlsOptions
    });

    client.bind( bindUser, bindPsw, function( err ) {

        if (err){
            logger.error("***Bind failed: " + err.message);
            res.status(500).send(err.message);
            return;
        }

        client.search( suffix, {filter:`(mail=${userEmail})`, scope:"sub"},
            (err, searchRes) => {
                ....

            });
                searchRes.on('error', function(err) {
                    console.error('error: ' + err.message);
                    res.status(500).send(err.message);
                });
                searchRes.on('end', function(result) {
                    if (!successSearchFlag){
                        res.status(500).send("User's email not found");
                        // console.log('End search status: ' + result.status);
                    }

            });
        });`
question unsupported

Most helpful comment

Just wanted to update: in my case GET request was sent by form itself. By default form sends GET requests, so together with my ajax request, form was sending also.
I've resolved it by stopping the GET:

$("#emailForm").bind("submit", function(e) {
     e.preventDefault();
     e.stopImmediatePropagation();
});

All 16 comments

I'm sorry, this sounds like a structure problem with your application code. The snippet you provide seems to be out of context. Is it in some request handler? Are you doing something after the client.bind that sends a response instead of waiting for the client to do its work?

I’m traveling today and don’t have access to my code. Will update soon.
And yes I have a code that sends response back to the client in couple cases when there’s an error and after client.bond I’m performing a user search. None of my responses triggered. When user re-submits corrected email address and on the server side client.bond triggered second time the browser refreshes the page. When execution continues on the server side first what is happening the client.bond finishes its work and my code continues. I’ll update with better code snippets later.

Sincerely,
Larry

On Jan 31, 2020, at 7:54 AM, James Sumners notifications@github.com wrote:


I'm sorry, this sounds like a structure problem with your application code. The snippet you provide seems to be out of context. Is it in some request handler? Are you doing something after the client.bind that sends a response instead of waiting for the client to do its work?

—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub, or unsubscribe.

Sorry for the delay. Here is the code of the client and server-side. I also made a video placing client and server debugging -> https://youtu.be/O_d4lNdZ8xM . You can see in the video when I submit email address for the second time, the page refreshes right after client.bind executes.

Client to load page to submit email address:
```html


Client page to submit email address:
```html
    <script>
        var emailAddr = document.getElementById("emailForm").elements["email"].value;

            $.ajax({
                    type: 'POST',
                    url: "/requestResetLink",
                    cache: false,
                    data: { 
                        email: JSON.stringify(emailAddr)
                    },
                    dataType: 'json',
                    success: function(data){

                        console.log(data);
                        ...

                    },                        
                    error: function(data, status, thrownError) {

                        // Have to show alert message instead of showing it in the <div> of this page
                        // and reload the page to avoid this page being directed back to login page.
                        alert(data.responseText);
                        window.location.reload();
                        console.log("ERROR requesting password reset link");
                        ...

                });


            </script>

Server side to process the request:

router.post('/requestResetLink', function(req, res) {

    if (req.body.email){

        var userEmail = JSON.parse(req.body.email);

        var tlsOptions = { 'rejectUnauthorized': false }
        client = ldap.createClient({
            url: ldapURL,
            tlsOptions: tlsOptions
        });


        // Client page refreshes right here, when request sent second time.
        // None of my statemets causing it
        client.bind( bindUser, bindPsw, function( err ) {

            if (err){
                logger.error("***Bind failed: " + err.message);
                console.log(err);
                res.status(500).send(err.message);
                return;
            }

            var successSearchFlag = false;

            client.search( suffix, {filter:`(mail=${userEmail})`, scope:"sub"},
                (err, searchRes) => {

                    if (err) {
                        logger.error("Bind successful, search failed. Ending session: " + err.message);
                        res.redirect("/logout");
                        return;
                    }

                    searchRes.on("searchEntry", (entry) => {    
                        successSearchFlag = true;
                        var userDN = entry.object.dn;
                        var userName = entry.object.sAMAccountName;

                        //Generate random link
                        var token = userVerification.getRandomToken();

                        // Generate timestamps
                        var dateNow = Date.now();
                        var currentTimestamp = moment(dateNow).format('YYYY-MM-DD HH:mm:ss');
                        var expiredTimestamp = moment(dateNow).add(1, 'hour').format('YYYY-MM-DD HH:mm:ss');

                        // Save data to the database...
                        // ...

                        // Generate email and send it to user...
                        // ...

                        // Once email is sent, send response back to client...
                        var response = {
                            status : 200,
                            message : 'Please check your email to reset your password.',
                        };

                        console.log(response.message);
                        res.end(JSON.stringify(response));

                    });
                    searchRes.on('error', function(err) {
                        // This was never executed during the test/debugging
                        console.error('error: ' + err.message);
                        res.status(500).send(err.message);
                    });

                    searchRes.on('end', function(result) {
                        if (!successSearchFlag){
                            console.log('End search status: ' + result.status);

                            res.status(500).send("User with the provided email not found. Please provide valid email or contact our team at https://app.slack.com/client/T1V6D3RT7/C6SRHPNSH to get help.");
                        }
                    });


            }); 

        });
    }

});

You might want to put a breakpoint in your client side code. Particularly at window.location.reload();.

You could also try catching emitted errors:

client = ldap.createClient({
    url: ldapURL,
    tlsOptions: tlsOptions
});

client.on("error", function( err ) {
    // handle error
}).on("setupError", (err) => {
    // handle setup error
}).on("socketTimeout", () => {
    // handle socket timeout
}).on("connectTimeout", () => {
    // handle connect timeout
}).on("connectRefused", () => {
    // handle connect refused
}).on("connectError", (err) => {
    // handle connect error
}).on("idle", () => {
    // handle idle
}).on("end", () => {
    // handle end
}).on("destroy", (err) => {
    // handle destroy
});


// Client page refreshes right here, when request sent second time.
// None of my statemets causing it
client.bind( bindUser, bindPsw, function( err ) {

The window.location.reload(); in the client's code - is the workaround this problem. I should not have to provide it at all, but now I think it maybe will help.
Normally when ajax receives the response we update an html object without reloading or navigating to another age (that is what ajax is about) but when I've encountered this behavior I start searching what is going on and found that simple page reload helps to avoid it.

Client code when

$.ajax({
    type: 'POST',
    url: "/requestResetLink",
    cache: false,
    data: { 
        email: JSON.stringify(emailAddr)
    },
    dataType: 'json',
    success: function(data){
        console.log(data);
        document.getElementById('notMsg').innerHTML = data.message;
        document.getElementById("notMsg").style.display = "block";

        document.getElementById("emailForm").elements["email"].value = "";
        document.getElementById("emailForm").style.display = "none";
 }, 
error: function(data, status, thrownError) {
    console.log("ERROR requesting password reset link");
    document.getElementById("emailForm").elements["email"].value = "";
    document.getElementById("errMsg").innerHTML = data.responseText;
    document.getElementById("notMsg").style.display = "none";
    document.getElementById("errMsg").style.display = "block";}
}

I've tried to catch emitted errors and there are no errors. As soon execution hits client.bind for the second time something is reloading the client with request: /requestlinkform?email=. I don't have this anywhere in my code, I do reload this form but without email paramater.

by something is reloading the client "client" refers to the users browser or the ldapjs client?

Sorry, it refers to the browser: something from insight client.bind sends a response /requestlinkform?email= to the browser. Basically saying reload this page but with the parameter. I don't have it anywhere in my code.

Unfortunately, this is very clearly an application design issue, not a demonstrated issue in the ldapjs library. Our time is very limited and we cannot be expected to support application design issues.

Please use https://stackoverflow.com/questions, https://softwareengineering.stackexchange.com/, or other similar resources for general application development questions

What version of ldapjs are you using?

I don't think there is anything inside the ldapjs code that responds directly to the request sent by the browser.

Try reducing the code to just the bind call and see if it still happens

router.post('/requestResetLink', function(req, res) {

    if (req.body.email){

        var userEmail = JSON.parse(req.body.email);

        var tlsOptions = { 'rejectUnauthorized': false }
        client = ldap.createClient({
            url: ldapURL,
            tlsOptions: tlsOptions
        });


        // Client page refreshes right here, when request sent second time.
        // None of my statemets causing it
        client.bind( bindUser, bindPsw, function( err ) {
            // simulate a success
            var response = {
                status : 200,
                message : 'Please check your email to reset your password.',
            };

            console.log(response.message);
            res.end(JSON.stringify(response));
        });
    }

});

Still getting same results...

try removing the bind call and seeing if you still get the same result

router.post('/requestResetLink', function(req, res) {

    if (req.body.email){

        var userEmail = JSON.parse(req.body.email);

        var tlsOptions = { 'rejectUnauthorized': false }
        client = ldap.createClient({
            url: ldapURL,
            tlsOptions: tlsOptions
        });

        // simulate a success
        var response = {
            status : 200,
            message : 'Please check your email to reset your password.',
        };

        console.log(response.message);
        res.end(JSON.stringify(response));


    }

});

hmm...still happening even after commenting everything except first if statement and response res.status(500).send("Simulating error response");

I would guess it is some code on the client side that is doing the redirect. Probably the $.ajax call not actually doing a POST method for some reason.

Your best bet is probably opening the dev tools and using preserve log in the network panel to see the actual requests and responses between page loads.

Just wanted to update: in my case GET request was sent by form itself. By default form sends GET requests, so together with my ajax request, form was sending also.
I've resolved it by stopping the GET:

$("#emailForm").bind("submit", function(e) {
     e.preventDefault();
     e.stopImmediatePropagation();
});

Happy you got it resolved.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

randomsock picture randomsock  Â·  7Comments

narendrachoudhary picture narendrachoudhary  Â·  7Comments

UziTech picture UziTech  Â·  4Comments

moehlone picture moehlone  Â·  3Comments

phi1ipp picture phi1ipp  Â·  4Comments