Skip to content
This repository has been archived by the owner on Feb 17, 2022. It is now read-only.

Commit

Permalink
Merge pull request #4 from DarkGhostHunter/master
Browse files Browse the repository at this point in the history
Fixed threshold error on reCAPTCHA validation: now it only passes it …
  • Loading branch information
DarkGhostHunter authored Jun 13, 2019
2 parents 1b3a0a2 + 7ede2ea commit c5f94e1
Show file tree
Hide file tree
Showing 12 changed files with 142 additions and 59 deletions.
30 changes: 20 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Captchavel by default works on `auto` mode, allowing you minimal configuration i

### Frontend

Just add the `data-recaptcha="true"` attribute to the forms where you want to have the reCAPTCHA check. The script will detect these forms an add a reCAPTCHA token to them so they can be checked in the backend.
Just add the `data-recaptcha="true"` attribute to the forms where you want to have the reCAPTCHA check. A JavaScript will be injected in all your responses that will detect these forms an add a reCAPTCHA token to them so they can be checked in the backend.

```blade
<form action="/login" method="post" data-recaptcha="true">
Expand All @@ -42,14 +42,16 @@ Just add the `data-recaptcha="true"` attribute to the forms where you want to ha
</form>
```

The Google reCAPTCHA script file from Google will be automatically injected on all responses for better analytics.
The Google reCAPTCHA script from Google will be automatically injected on all responses for better analytics.

> Check the `manual` mode if you want control on how to deal with the frontend reCAPTCHA script.
### Backend

After that, you should add the `recaptcha` middleware inside your controllers that will receive input and you want to *protect* with the reCAPTCHA check.

You can use the `isHuman()` and `isRobot()` methods in the Request instance to check if the request was made by a human or a robot, respectively.

```php
<?php

Expand Down Expand Up @@ -81,13 +83,13 @@ class CustomController extends Controller
'username' => 'required|string|exists:users,username'
]);

// ...
if ($request->isRobot()) {
return response()->view('web.user.pending_approval');
}

return response()->view('web.user.success');

}

// ...
}
```

Expand All @@ -105,7 +107,8 @@ Route::post('form')->uses('CustomController@form')->middleware('recaptcha');
### Accessing the reCAPTCHA response

You can access the reCAPTCHA response in four ways ways:
You can access the reCAPTCHA response in four ways:

* using [dependency injection](https://laravel.com/docs/container#automatic-injection),
* using the `ReCaptcha` facade anywhere in your code,
* the `recaptcha()` helper,
Expand Down Expand Up @@ -139,6 +142,7 @@ class CustomController extends Controller
* @param \Illuminate\Http\Request $request
* @param \DarkGhostHunter\Captchavel\ReCaptcha $reCaptcha
* @return \Illuminate\Http\Response
* @throws \DarkGhostHunter\Captchavel\Exceptions\RecaptchaNotResolvedException
*/
public function form(Request $request, ReCaptcha $reCaptcha)
{
Expand All @@ -164,6 +168,8 @@ The class has handy methods you can use to check the status of the reCAPTCHA inf
* `isRobot()`: Detects if the Request has been made by a Robot (below threshold).
* `since()`: Returns the time the reCAPTCHA challenge was resolved as a Carbon timestamp.

> If you try to check if the response while the reCAPTCHA wasn't resolved, you will get a `RecaptchaNotResolvedException`.
## Local development and robot requests

When developing, this package registers a transparent middleware that allows you to work on your application without contacting reCAPTCHA servers ever. Instead, it will always generate a successful "dummy" response with a `1.0` score.
Expand Down Expand Up @@ -191,7 +197,7 @@ POST http://myapp.com/login?is_robot

If you want to connect to the reCAPTCHA servers on `local` environment, you can set the `CAPTCHAVEL_LOCAL=true` in your `.env` file.

> The transparent middleware also registers itself on testing environment, so you can test your application using requests made by a robot and made by a human just adding an empty `_recaptcha` input.
> The transparent middleware also registers itself on testing environment, so you can test your application using requests made by a robot and made by a human just adding an empty `_recaptcha` input. Sweet!
## Configuration

Expand Down Expand Up @@ -268,7 +274,7 @@ return [

### Key and Secret

There parameters are self-explanatory. One is the reCAPTCHA Site Key, which is shown publicly in your views, and the Secret, which is used to recover the user interaction information privately inside your application.
These parameters are self-explanatory. One is the reCAPTCHA Site Key, which is shown publicly in your views, and the Secret, which is used to recover the user interaction information privately inside your application.

If you don't have them, use the [Google reCAPTCHA Admin console](https://g.co/recaptcha/admin) to create a pair.

Expand All @@ -289,8 +295,12 @@ Aside from that, you can also override the score using a parameter within the `r

use Illuminate\Support\Facades\Route;

Route::post('{product]/review')
->uses('ReviewController@create')
Route::post('{product}/comments')
->uses('Product/CommentController@create')
->middleware('recaptcha:0.3');

Route::post('{product}/review')
->uses('Product/ReviewController@create')
->middleware('recaptcha:0.8');
```

Expand Down
82 changes: 47 additions & 35 deletions src/CaptchavelServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,59 +6,55 @@
use DarkGhostHunter\Captchavel\Http\Middleware\InjectRecaptchaScript;
use DarkGhostHunter\Captchavel\Http\Middleware\TransparentRecaptcha;
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Http\Request;
use Illuminate\Routing\Router;
use Illuminate\Support\ServiceProvider;
use ReCaptcha\ReCaptcha as ReCaptchaFactory;

class CaptchavelServiceProvider extends ServiceProvider
{
/**
* Register the application services.
*
* @return void
*/
public function register()
{
// Automatically apply the package configuration
$this->mergeConfigFrom(__DIR__ . '/../config/captchavel.php', 'captchavel');

// When the application tries to resolve the ReCaptcha instance, we will pass the Site Key.
$this->app->when(ReCaptchaFactory::class)
->needs('$secret')
->give(function ($app) {
return $app->make('config')->get('captchavel.secret');
});

$this->app->singleton('recaptcha', ReCaptcha::class);
}

/**
* Bootstrap the application services.
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function boot()
{
/*
* Optional methods to load your package assets
*/
// $this->loadTranslationsFrom(__DIR__.'/../resources/lang', 'captchavel');
$this->loadViewsFrom(__DIR__.'/../resources/views', 'captchavel');
// $this->loadMigrationsFrom(__DIR__.'/../database/migrations');
// $this->loadRoutesFrom(__DIR__.'/routes.php');
$this->loadViewsFrom(__DIR__ . '/../resources/views', 'captchavel');

if ($this->app->runningInConsole()) {
$this->publishes([
__DIR__.'/../config/captchavel.php' => config_path('captchavel.php'),
__DIR__ . '/../config/captchavel.php' => config_path('captchavel.php'),
], 'config');

// Publishing the views.
$this->publishes([
__DIR__.'/../resources/views' => resource_path('views/vendor/captchavel'),
__DIR__ . '/../resources/views' => resource_path('views/vendor/captchavel'),
], 'views');
}

$this->bootMiddleware();
}

/**
* Register the application services.
*
* @void
*/
public function register()
{
// Automatically apply the package configuration
$this->mergeConfigFrom(__DIR__.'/../config/captchavel.php', 'captchavel');

// When the application tries to resolve the ReCaptcha instance, we will pass the Site Key.
$this->app->when(ReCaptchaFactory::class)
->needs('$secret')
->give(function ($app) {
return $app->make('config')->get('captchavel.secret');
});

$this->app->singleton('recaptcha', ReCaptcha::class);
$this->extendRequestMacro();
}

/**
Expand All @@ -76,9 +72,9 @@ protected function bootMiddleware()
// and package config. If we shouldn't, we will register a transparent middleware in the
// application to avoid the errors when the "recaptcha" is used but not registered.
if ($this->shouldEnableMiddleware()) {
$this->registerMiddleware($router);
$this->addMiddleware($router);
} else {
$this->registerTransparentMiddleware($router);
$this->addTransparentMiddleware($router);
}
}

Expand All @@ -97,10 +93,10 @@ protected function shouldEnableMiddleware()
/**
* Registers real middleware for the package
*
* @param \Illuminate\Routing\Router $router
* @param \Illuminate\Routing\Router $router
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
protected function registerMiddleware(Router $router)
protected function addMiddleware(Router $router)
{
$router->aliasMiddleware('recaptcha', CheckRecaptcha::class);
$router->aliasMiddleware('recaptcha-inject', InjectRecaptchaScript::class);
Expand All @@ -113,11 +109,27 @@ protected function registerMiddleware(Router $router)
/**
* Registers a Dummy (Transparent) Middleware
*
* @param \Illuminate\Routing\Router $router
* @param \Illuminate\Routing\Router $router
*/
protected function registerTransparentMiddleware(Router $router)
protected function addTransparentMiddleware(Router $router)
{
$router->aliasMiddleware('recaptcha', TransparentRecaptcha::class);
}

/**
* Extend the Request with a couple of macros
*
* @return void
*/
protected function extendRequestMacro()
{
Request::macro('isHuman', function () {
return recaptcha()->isHuman();
});

Request::macro('isRobot', function () {
return recaptcha()->isRobot();
});
}

}
8 changes: 8 additions & 0 deletions src/Exceptions/CaptchavelException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace DarkGhostHunter\Captchavel\Exceptions;

interface CaptchavelException
{

}
6 changes: 4 additions & 2 deletions src/Exceptions/FailedRecaptchaException.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@

use Exception;

class FailedRecaptchaException extends Exception
class FailedRecaptchaException extends Exception implements CaptchavelException
{
public function __construct(array $errorCodes)
{
$this->message = "The Google reCAPTCHA library returned the following errors: \n" .
$this->message = 'The Google reCAPTCHA library returned the following errors:' .
implode("\n- ", $errorCodes);

parent::__construct();
}
}
2 changes: 1 addition & 1 deletion src/Exceptions/InvalidCaptchavelMiddlewareMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use Exception;

class InvalidCaptchavelMiddlewareMethod extends Exception
class InvalidCaptchavelMiddlewareMethod extends Exception implements CaptchavelException
{
protected $message = 'Captchavel does not work in GET routes.';
}
2 changes: 1 addition & 1 deletion src/Exceptions/InvalidRecaptchaException.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use Exception;
use Throwable;

class InvalidRecaptchaException extends Exception
class InvalidRecaptchaException extends Exception implements CaptchavelException
{
protected $message = 'The reCAPTCHA token is empty';

Expand Down
10 changes: 10 additions & 0 deletions src/Exceptions/RecaptchaNotResolvedException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace DarkGhostHunter\Captchavel\Exceptions;

use Exception;

class RecaptchaNotResolvedException extends Exception implements CaptchavelException
{
protected $message = 'The reCAPTCHA has not been verified in this Request.';
}
3 changes: 1 addition & 2 deletions src/Http/Middleware/CheckRecaptcha.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,8 @@ protected function resolve(Request $request, float $threshold)
return $this->reCaptcha->setResponse(
$this->reCaptchaFactory
->setExpectedAction($this->sanitizeAction($request->getRequestUri()))
->setScoreThreshold($threshold)
->verify($request->input('_recaptcha'), $request->getClientIp())
);
)->setThreshold($threshold);
}

/**
Expand Down
14 changes: 13 additions & 1 deletion src/ReCaptcha.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace DarkGhostHunter\Captchavel;

use DarkGhostHunter\Captchavel\Exceptions\RecaptchaNotResolvedException;
use Illuminate\Support\Carbon;
use ReCaptcha\Response;

Expand Down Expand Up @@ -48,7 +49,7 @@ public function setResponse(Response $response)
*/
public function isResolved()
{
return !is_null($this->response);
return $this->response !== null;
}

/**
Expand Down Expand Up @@ -78,16 +79,22 @@ public function setThreshold(float $threshold)
* Return if the Response was made by a Human
*
* @return bool
* @throws \DarkGhostHunter\Captchavel\Exceptions\RecaptchaNotResolvedException
*/
public function isHuman()
{
if (!$this->response) {
throw new RecaptchaNotResolvedException();
}

return $this->response->getScore() >= $this->threshold;
}

/**
* Return if the Response was made by a Robot
*
* @return bool
* @throws \DarkGhostHunter\Captchavel\Exceptions\RecaptchaNotResolvedException
*/
public function isRobot()
{
Expand All @@ -108,9 +115,14 @@ public function response()
* Return the reCAPTCHA Response timestamp as a Carbon instance
*
* @return \Illuminate\Support\Carbon
* @throws \DarkGhostHunter\Captchavel\Exceptions\RecaptchaNotResolvedException
*/
public function since()
{
if (!$this->response) {
throw new RecaptchaNotResolvedException();
}

return $this->since ?? $this->since = Carbon::parse($this->response->getChallengeTs());
}
}
7 changes: 0 additions & 7 deletions tests/Middleware/CheckRecaptchaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,6 @@ public function testMiddlewareAcceptsParameter()
$mockReCaptchaFactory->shouldReceive('setExpectedAction')
->once()
->andReturnSelf();
$mockReCaptchaFactory->shouldReceive('setScoreThreshold')
->once()
->withArgs(function ($threshold) {
$this->assertEquals(0.9, $threshold);
return true;
})
->andReturnSelf();
$mockReCaptchaFactory->shouldReceive('verify')
->once()
->andReturn(new Response(true, [], null, null, null, 1.0, null));
Expand Down
Loading

0 comments on commit c5f94e1

Please sign in to comment.