desirepath41 / visualCaptcha-PHP

visualCaptcha for PHP
MIT License
101 stars 29 forks source link

visualCaptcha in Laravel 5.1 #21

Closed redreeva closed 8 years ago

redreeva commented 8 years ago

Hello! Tell me please, can I use visualCaptcha without form submitted? Captcha displayed, refresh and audio buttons, "check is filled" are working but when I check captcha is nothing happens, here

$frontendData = $captcha->getFrontendData();

is nothing contains. I hope you find problem :)

.routes.php

Route::get('/start/{params}', function($params) {
    $session = new \visualCaptcha\Session();
    $captcha = new \visualCaptcha\Captcha($session);
    $captcha->generate($params);
    $response[ 'Content-Type' ] = 'application/json';
    echo json_encode( $captcha->getFrontEndData() );
});
Route::get('/image/{index}', function($index) {
    $session = new \visualCaptcha\Session();
    $captcha = new \visualCaptcha\Captcha($session);
    return $captcha->streamImage(array(), $index, 0);
});
Route::get('/audio/{type}', function($type) {
    $session = new \visualCaptcha\Session();
    $captcha = new \visualCaptcha\Captcha($session);
    return $captcha->streamAudio(array(), $type);
});
Route::post('/try', function() {
    $session = new \visualCaptcha\Session();
    $captcha = new \visualCaptcha\Captcha( $session );
    $frontendData = $captcha->getFrontendData();

    if ( $imageAnswer = Request::params( $frontendData[ 'imageFieldName' ] ) ) {
        if ( $captcha->validateImage( $imageAnswer ) ) {
            return 'ok';
        } else {
            return 'no';
        }
    } else if ( $audioAnswer = Request::params( $frontendData[ 'audioFieldName' ] ) ) {
        if ( $captcha->validateAudio( $audioAnswer ) ) {
            return 'ok';
        } else {
            return 'no';
        }
    } else {
        return 'empty';
    }
});

view:

...
<div id="sample-captcha"></div>
<div id="check-is-filled">Check is filled</div>
<div id="status-message"></div>
...

captcha.js:

$( function() {
    var captchaEl = $( '#sample-captcha' ).visualCaptcha({
        imgPath: 'images/captcha/',
        captcha: {
            numberOfImages: 3,
            callbacks: {
                loaded: function( captcha ) {
                    // Avoid adding the hashtag to the URL when clicking/selecting visualCaptcha options
                    $( '#sample-captcha a' ).on( 'click', function( event ) {
                        event.preventDefault();
                    });
                }
            }

        }
    } );
    var captcha = captchaEl.data( 'captcha' );

     // Show an alert saying if visualCaptcha is filled or not
      var _sayIsVisualCaptchaFilled = function( event ) {
        event.preventDefault();

        if ( captcha.getCaptchaData().valid ) {
            window.alert( 'visualCaptcha is filled!' );
        } else {
            window.alert( 'visualCaptcha is NOT filled!' );
        }
    };

    var statusEl = $( '#status-message' ),
        queryString = window.location.search;
    // Show success/error messages
    if ( queryString.indexOf('status=noCaptcha') !== -1 ) {
        statusEl.html('<div class="status"> <div class="icon-no"></div> <p>visualCaptcha was not started!</p> </div>');
    } else if ( queryString.indexOf('status=validImage') !== -1 ) {
        statusEl.html('<div class="status valid"> <div class="icon-yes"></div> <p>Image was valid!</p> </div>');
    } else if ( queryString.indexOf('status=failedImage') !== -1 ) {
        statusEl.html('<div class="status"> <div class="icon-no"></div> <p>Image was NOT valid!</p> </div>');
    } else if ( queryString.indexOf('status=validAudio') !== -1 ) {
        statusEl.html('<div class="status valid"> <div class="icon-yes"></div> <p>Accessibility answer was valid!</p> </div>');
    } else if ( queryString.indexOf('status=failedAudio') !== -1 ) {
        statusEl.html('<div class="status"> <div class="icon-no"></div> <p>Accessibility answer was NOT valid!</p> </div>');
    } else if ( queryString.indexOf('status=failedPost') !== -1 ) {
        statusEl.html('<div class="status"> <div class="icon-no"></div> <p>No visualCaptcha answer was given!</p> </div>');
    }

    // Bind that function to the appropriate link
    $( '#check-is-filled' ).on( 'click.app', _sayIsVisualCaptchaFilled );
} );

if clicked captcha checked button:

$.ajax({
            url: '/try',
            type: 'POST',
            headers: {
                'X-CSRF-TOKEN': $('[name="_token"]').val()
            },
            data: {data: 1},
            success: function(data) {
                console.log(data);
            }
        });

Thank you!

BrunoBernardino commented 8 years ago

Yes, it's possible.

You're not sending what the captcha is creating though, so you should wrap the <div id="sample-captcha"></div> in a <form> which you can then serialize and send in the ajax request.

Let me know if you need any more details.

redreeva commented 8 years ago

Bruno, thank you for response!

I do exactly what you mean, my captcha is in form and when I click "Send form" at first fires fields and captcha validation. Main question - what I do wrong? Why when form post and fires captcha validation this field

$frontendData = $captcha->getFrontendData();

is empty?

redreeva commented 8 years ago

Bruno, I decided to look again this file visualCaptcha-PHP/app/app.php and I noticed this code at the top:

<?php
// Allow requests from all origins
$app->response[ 'Access-Control-Allow-Origin' ] = '*';
// Inject Session closure into app
$app->session = function() use( $app ) {
    if ( $namespace = $app->request->params( 'namespace' ) ) {
        $session = new \visualCaptcha\Session( 'visualcaptcha_' . $namespace );
    } else {
        $session = new \visualCaptcha\Session();
    }
    return $session;
};

I didn't do namespace check. Honestly i don't know how this check correctly implement in laravel. Is it important? I still in finding problem and understood why in captcha validation is nothing to check: we nothing sent, we just create a new object:

$session = new \visualCaptcha\Session();
$captcha = new \visualCaptcha\Captcha($session);
...

How can this work?

BrunoBernardino commented 8 years ago

Hi, Eugeniya.

You don't need to worry about namespacing if you don't use more than one visualCaptcha instance per page.

I don't think you're sending the form data, but you say you are.

Can you add this before the $frontendData = $captcha->getFrontendData(); line:

echo '<pre>';
var_dump($_POST);
echo '</pre>';

And see what it shows in the ajax response?

If there's a public URL to see all this working together, it might be easier to spot the issue too.

redreeva commented 8 years ago

Bruno, code you proposed displayed next:

<pre>
array(1) {
  ["code"]=>
  string(1) "FasXX-12-GG"
}
</pre>

It's because I send in POST only text field value.

$.ajax({
            url: '/game/checkCode' ,
            type: 'POST',
            headers: {
                'X-CSRF-TOKEN': $('[name="_token"]').val()
            },
            data: { 'code': $('#start-form input[name=code]').val() },
            success: function(data) {
                ...
            }
        });

I thought that I don't should send captcha value in post manually... Maybe my request is written incorrectly?

BrunoBernardino commented 8 years ago

If you're not sending it, how will the app know about it? :)

You need to send it, that's why I said you should serialize the whole form. You can't reliably tell what the input name will be, or what the value is, that's handled by visualCaptcha itself.

redreeva commented 8 years ago

Ok) In this case what value I have to send: selected image receives class 'visualCaptcha-selected', so I should to pass parameter of image data-index? And if it's true, so what I should compare with received value?

BrunoBernardino commented 8 years ago

The comparison's done in the logic you see in the /try route.

You don't need to pass any specific parameters. Like I said, it's best to send all inputs as they are, the idea is that you can't know for sure what will the input name be, or the value. It's all hashes, and the correct mapping is stored in the server session.

redreeva commented 8 years ago

Bruno, you're right about serialize - now POST display captcha info:

data: "_token=WhH4gCMD7s6aBVJytPNp6ihU7vsdgv3eRysJIk0m&code=d&97e937faee4a4820a11f=ffe8dd4a3f1250dd445f"

But unfortunately result of this string:

$frontendData = $captcha->getFrontendData();

still is empty object.

BrunoBernardino commented 8 years ago

That's what POST shows in the backend? How are you doing it in the $.ajax bit?

redreeva commented 8 years ago

Yes.

$.ajax({
            url: '/game/checkCode',
            type: 'POST',
            headers: {
                'X-CSRF-TOKEN': $('[name="_token"]').val()
            },
            data: { data: $("form").serialize() },
            success: function(data) {
                ...
            }
        });
BrunoBernardino commented 8 years ago

Ok, change it to:

        $.ajax({
            url: '/game/checkCode',
            type: 'POST',
            headers: {
                'X-CSRF-TOKEN': $('[name="_token"]').val()
            },
            data: $("form").serialize(),
            success: function(data) {
                ...
            }
        });

So that the form data is sent as expected, instead of inside a data property.

redreeva commented 8 years ago

Now POST returns:

_token: "WhH4gCMD7s6aBVJytPNp6ihU7vsdgv3eRysJIk0m", code: "ddd", 12781b83c23b681fc7e1: "14b3d70b15cbe89a6838"

$frontendData is empty.

BrunoBernardino commented 8 years ago

hmm. Can you see any session values?

visualCaptcha\Session tries to use $_SESSION, so if that doesn't work, you should either change it to some other Session class, like Laravel's, or try adding session_start(); in the beginning of the routes file, I guess.

redreeva commented 8 years ago

Laravel Session class contains:

Object {_token: "WhH4gCMD7s6aBVJytPNp6ihU7vsdgv3eRysJIk0m", _previous: Object, flash: Object}

and $_SESSION shows:

Object {visualcaptcha: Array[0]}
BrunoBernardino commented 8 years ago

Ok, turns out you need a couple of things for the XHR request:

  1. Send your session cookie, so the session is recognized, including its values
  2. Allow the back-end to make such a request

Looking at http://stackoverflow.com/questions/2870371/why-is-jquerys-ajax-method-not-sending-my-session-cookie you should try changing your $.ajax code to:

        $.ajax({
            url: '/game/checkCode',
            type: 'POST',
            xhrFields: {
              withCredentials: true
            }
            headers: {
                'X-CSRF-TOKEN': $('[name="_token"]').val()
            },
            data: $("form").serialize(),
            success: function(data) {
                ...
            }
        });

If you get an error about CORS, try changing the first lines in app/app.php from:

// Allow requests from all origins
$app->response[ 'Access-Control-Allow-Origin' ] = '*';

to:

// Allow requests from all origins
$app->response[ 'Access-Control-Allow-Origin' ] = 'http://YOURDOMAIN.com';
$app->response[ 'Access-Control-Allow-Credentials' ] = true;

And make sure to change YOURDOMAIN.com to what you're using.

Let me know how that works out.

redreeva commented 8 years ago

Maybe it's a little bit late for asking this question... :) Your captcha should to work on localhost?

BrunoBernardino commented 8 years ago

Yes, but it won't work for Chrome: http://stackoverflow.com/questions/10883211/deadly-cors-when-http-localhost-is-the-origin you can easily do a workaround, like what's mentioned in that URL.

redreeva commented 8 years ago

Bruno, thank you for yours fast replies, but nothing helps. I'll look for solutions for Laravel.

BrunoBernardino commented 8 years ago

Ok. There are a couple of unofficial Laravel versions, see if any of those work for you: https://github.com/emotionLoop/visualCaptcha#unofficial-versions-created-by-community-members