diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml
index 373ece8..a6da838 100644
--- a/.github/workflows/php.yml
+++ b/.github/workflows/php.yml
@@ -12,13 +12,11 @@ jobs:
fail-fast: true
matrix:
php: [7.4, 7.3, 7.2.15]
- laravel: [7.*, 6.*]
+ laravel: [7.*]
dependency-version: [prefer-lowest, prefer-stable]
include:
- laravel: 7.*
testbench: 5.*
- - laravel: 6.*
- testbench: ^4.1
name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - ${{ matrix.dependency-version }}
diff --git a/README.md b/README.md
index 5282457..7429a05 100644
--- a/README.md
+++ b/README.md
@@ -6,13 +6,27 @@
# Captchavel
-Easily integrate Google reCAPTCHA v3 into your Laravel application.
+Integrate reCAPTCHA into your Laravel app better than the Big G itself!
-> This is totally compatible with reCAPTCHA v2, so you can use both. Check [this GitHub comment](https://github.com/google/recaptcha/issues/279#issuecomment-445529732) about the caveats.
+It uses your Laravel HTTP Client and **HTTP/2**, making your app **fast**. You only need a couple of lines to integrate.
+
+## Table of Contents
+
+* [Requirements](#requirements)
+* [Installation](#installation)
+* [Set Up](#set-up)
+* [Usage](#usage)
+ - [Checkbox, Invisible and Android challenges](#checkbox-invisible-and-android-challenges)
+ - [Score driven interaction](#score-driven-interaction)
+* [Frontend integration](#frontend-integration)
+* [Advanced configuration](#advanced-configuration)
+* [Testing with Captchavel](#testing-with-captchavel)
+* [Security](#security)
+* [License](#license)
## Requirements
-* Laravel 6 or Laravel 7
+* Laravel 7.x
## Installation
@@ -22,213 +36,141 @@ You can install the package via composer:
composer require darkghosthunter/captchavel
```
-## Usage
+## Set up
-The first thing you need is to add the `RECAPTCHA_V3_KEY` and `RECAPTCHA_V3_SECRET` environment variables in your `.env` file with your reCAPTCHA Site Key and Secret Key, respectively. If you don't have them, you should go to your [Google reCAPTCHA Admin console](https://g.co/recaptcha/admin) and create them for your application.
+Add the reCAPTCHA keys for your site to the environment file of your project. You can add each of them for reCAPTCHA v2 **checkbox**, **invisible**, **Android**, and **v3** (score).
-```dotenv
-RECAPTCHA_V3_KEY=JmXJEeOqjHkr9LXEzgjuKsAhV84RH--DvRJo5mXl
-RECAPTCHA_V3_SECRET=JmXJEeOqjHkr9WjDR4rjuON1MGxqCxdOA4zDTH0w
-```
+If you don't have one, generate it in your [reCAPTCHA Admin panel](https://www.google.com/recaptcha/admin/).
-Captchavel by default works on `auto` mode, allowing you minimal configuration in the backend and frontend. Let's start with the latter.
-
-### Frontend
-
-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.
+```dotenv
+RECAPTCHA_V2_CHECKBOX_SECRET=6t5geA1UAAAAAN...
+RECAPTCHA_V2_CHECKBOX_KEY=6t5geA1UAAAAAN...
-```blade
-
-```
+RECAPTCHA_V2_INVISIBLE_SECRET=6t5geA2UAAAAAN...
+RECAPTCHA_V2_INVISIBLE_KEY=6t5geA2UAAAAAN...
-The Google reCAPTCHA script from Google will be automatically injected on all responses for better analytics.
+RECAPTCHA_V2_ANDROID_SECRET=6t5geA3UAAAAAN...
+RECAPTCHA_V2_ANDROID_KEY=6t5geA3UAAAAAN...
-> Alternatively, you may want to use the [`manual` mode](#manual) if you want control on how to deal with the frontend reCAPTCHA script, or use a [personalized one](#editing-the-script-view).
+RECAPTCHA_V3_SECRET=6t5geA4UAAAAAN...
+RECAPTCHA_V3_KEY=6t5geA4UAAAAAN...
+```
-#### Form submission prevented
+This allows you to check different reCAPTCHA mechanisms using the same application, in different environments.
-Form submission is disabled by default until the token from reCAPTCHA is retrieved. If you want to disable this behaviour, append `data-recaptcha-dont-prevent` to the form:
+> Captchavel already comes with v2 keys for local development. For v3, you will need to create your own set of credentials.
-```blade
-
-
-```
+## Usage
-#### Token resolved helper
+After you integrate reCAPTCHA into your frontend or Android app, set the Captchavel middleware in the routes you want:
-When the reCAPTCHA token is being retrieved for the form, the form will have the property `recaptcha_unresolved` set to `true`. You can use this property for your other scripts to conditionally allow submission or whatever.
+* `recaptcha.v2` for Checkbox, Invisible and Android challenges.
+* `recaptcha.v3` for Score driven interaction.
-```javascript
-if (form.recaptcha_unresolved) {
- alert('Wait until reCAPTCHA sends the token!');
-} else {
- form.submit();
-}
-```
+### Checkbox, Invisible and Android challenges
-### Backend
+Add the `recaptcha.v2` middleware to your `POST` routes. The middleware will catch the `g-recaptcha-response` input and check if it's valid.
-After that, you should add the `recaptcha` middleware inside your controllers that will receive input and you want to *protect* with the reCAPTCHA check.
+* `recaptcha.v2:checkbox` for explicitly rendered checkbox challenges.
+* `recaptcha.v2:invisible` for invisible challenges.
+* `recaptcha.v2:android` for Android app challenges.
-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.
+When the validation fails, the user will be redirected back to the form route, or a JSON response will be returned with the validation errors.
```php
-middleware('recaptcha.v2:checkbox');
+```
-namespace App\Http\Controllers;
+> You can change the input name from `g-recaptcha-response` to a custom using a second parameter, like `recaptcha.v2:checkbox,_recaptcha`.
-use Illuminate\Http\Request;
+### Score driven interaction
-class CustomController extends Controller
-{
- /**
- * Create a new CustomController instance
- *
- * @return void
- */
- public function __construct()
- {
- $this->middleware('recaptcha')->only('form');
- }
-
- /**
- * Receive the HTTP POST Request
- *
- * @param \Illuminate\Http\Request $request
- * @return \Illuminate\Http\Response
- */
- public function form(Request $request)
- {
- $request->validate([
- 'username' => 'required|string|exists:users,username'
- ]);
-
- if ($request->isRobot()) {
- return response()->view('web.user.pending_approval');
- }
-
- return response()->view('web.user.success');
-
- }
-}
-```
+The reCAPTCHA v3 middleware works differently from v2. This is a score-driven challenge between `0.0` and `1.0` where robots will get lower scores than humans. The default threshold is `0.5`.
-Since it's a middleware, you can alternatively set it inside your route declaration:
+Simply add the `recaptcha.v3` middleware to your route:
```php
-uses('CustomController@form')->middleware('recaptcha');
+Route::post('comment', 'CommentController@store')
+ ->middleware('recaptcha.v3');
```
-> The `recaptcha` middleware only works on `POST/PUT/PATCH/DELETE` methods, so don't worry if you use it in a `GET` method. You will receive a nice `InvalidMethodException` so you can use it correctly.
-
-### Accessing the reCAPTCHA response
-
-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,
-* and resolving it from the Service Container with `app('recaptha')`.
-
-These methods will return the reCAPTCHA Response from the servers, with useful helpers so you don't have to dig in the raw response:
+Once the challenge has been received, you will have access to two methods from the Request instance: `isHuman()` and `isRobot()`, which return `true` or `false`:
```php
-middleware('recaptcha')->only('form');
- }
+ $request->validate([
+ 'body' => 'required|string|max:255'
+ ]);
- /**
- * Receive the HTTP POST Request
- *
- * @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)
- {
- $request->validate([
- 'username' => 'required|string|exists:users,username'
- ]);
-
- if ($reCaptcha->isRobot()) {
- return response()->view('web.user.you-are-a-robot');
- }
-
- return response()->view('web.user.success');
- }
+ $comment = $post->comment()->make($request->only('body'));
- // ...
+ // Flag the comment as "moderated" if it was a written by robot.
+ $comment->moderated = $request->isRobot();
+
+ $comment->save();
+
+ return view('post.comment.show', ['comment' => $comment]);
}
```
-The class has handy methods you can use to check the status of the reCAPTCHA information:
+#### Threshold, action and input name
-* `isResolved()`: Returns if the reCAPTCHA check was made in the current Request
-* `isHuman()`: Detects if the Request has been made by a Human (equal or above threshold).
-* `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.
+The middleware accepts three parameters in the following order:
-> If you try to check if the response while the reCAPTCHA wasn't resolved, you will get a `RecaptchaNotResolvedException`.
+1. Threshold: Values **above or equal** are considered human.
+2. Action: The action name to optionally check against.
+3. Input: The name of the reCAPTCHA input to verify.
-## Local development and robot requests
+```php
+Route::post('comment', 'CommentController@store')
+ ->middleware('recaptcha.v3:0.7,login,custom-recaptcha-input');
+```
-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.
+> When checking the action name, ensure your Frontend action matches
-You can override the score to an absolute `0.0` in two ways:
+#### Faking robot and human scores
-* appending the `is_robot` key to the Request query,
+You can easily fake a reCAPTCHA v3 response in your local development by setting `CAPTCHAVEL_FAKE` to `true`.
-```http request
-POST http://myapp.com/login?is_robot
+```dotenv
+CAPTCHAVEL_FAKE=true
```
-* or adding a checkbox with the name `is_robot` checked.
+Then, you can fake a low-score response by adding an `is_robot` a checkbox, respectively.
-```html
-
```
-If you want to connect to the reCAPTCHA servers on `local` environment, you can set the `CAPTCHAVEL_LOCAL=true` in your `.env` file.
+## Frontend integration
+
+[Check the official reCAPTCHA documentation](https://developers.google.com/recaptcha/intro) to integrate the reCAPTCHA script in your frontend, or inside your Android application.
+
+You can use the `captchavel()` helper to output the site key depending on the challenge version you want to render: `checkbox`, `invisible`, `android` or `score` (v3).
+
+```blade
+
+```
-> 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!
+> You can also retrieve the key using `android` for Android apps.
-## Configuration
+## Advanced configuration
-For finner configuration, publish the configuration file for Captchavel:
+Captchavel is intended to work out-of-the-box, but you can publish the configuration file for fine-tuning and additional reCAPTCHA verification.
```bash
php artisan vendor:publish --provider="DarkGhostHunter\Captchavel\CaptchavelServiceProvider"
@@ -240,215 +182,151 @@ You will get a config file with this array:
env('CAPTCHAVEL_MODE', 'auto'),
- 'enable_local' => env('CAPTCHAVEL_LOCAL', false),
- 'key' => env('RECAPTCHA_V3_KEY'),
- 'secret' => env('RECAPTCHA_V3_SECRET'),
+ 'enable' => env('CAPTCHAVEL_ENABLE', false),
+ 'fake' => env('CAPTCHAVEL_FAKE', false),
+ 'hostname' => env('RECAPTCHA_HOSTNAME'),
+ 'apk_package_name' => env('RECAPTCHA_APK_PACKAGE_NAME'),
'threshold' => 0.5,
- 'request_method' => null,
+ 'credentials' => [
+ // ...
+ ]
];
-```
-
-### Mode
+```
-Captchavel works painlessly once installed. You can modify the behaviour with just changing the `CAPTCHAVEL_MODE` to `auto` or `manual`, since the config file just picks up the environment file values.
+### Enable Switch
```dotenv
-CAPTCHAVEL_MODE=auto
+CAPTCHAVEL_ENABLE=true
```
-#### `auto`
-
-The `auto` option leverages the frontend work from you. Just add the `data-recaptcha="true"` attribute to the forms where you want to check for reCAPTCHA.
-
-```blade
-
-```
+The main switch to enable or disable Captchavel middleware. This can be handy to enable on some local environments to check real interaction using the included localhost test keys.
-Captchavel will inject the Google reCAPTCHA v3 as a deferred script before `` tag, in every response (except JSON, AJAX or anything non-HTML), so it can have more analytics about how users interact with your site.
+When switched off, the `g-recaptcha-response` won't be validated in the Request input, so you can safely disregard any frontend script or reCAPTCHA tokens or boxes.
-To override the script that gets injected, take a look in the [editing the script view](#editing-the-script-view) section.
+### Fake responses
-#### `manual`
+```dotenv
+CAPTCHAVEL_FAKE=true
+```
-This will disable the global middleware that injects the Google reCAPTCHA script in your frontend. You should check out the [Google reCAPTCHA documentation](https://developers.google.com/recaptcha/docs/v3) on how to implement it yourself.
+Setting this to true will allow your application to [fake v3-score responses from reCAPTCHA servers](#faking-robot-and-human-scores).
-Since the frontend won't have nothing injected, this mode it gives you freedom to:
+> This is automatically set to `true` when [running unit tests](#testing-with-captchavel).
-* manually include the `recaptcha-inject` middleware only in the routes you want,
-* or include the `recaptcha::script` blade template in your layouts you want.
+### Hostname and APK Package Name
-> The manual mode is very handy if your responses have a lot of data and want better performance, because the middleware won't look into the responses.
+```dotenv
+RECAPTCHA_HOSTNAME=myapp.com
+RECAPTCHA_APK_PACKAGE_NAME=my.package.name
+```
-### Enable on Local Environment
+If you are not verifying the Hostname or APK Package Name in your [reCAPTCHA Admin Panel](https://www.google.com/recaptcha/admin/), you will have to issue the strings in the environment file.
-By default, this package is transparent on `local` and `testing` environments, so you can develop without requiring to use reCAPTCHA anywhere.
+When the reCAPTCHA response from the servers is retrieved, it will be checked against these values when present. In case of mismatch, a validation exception will be thrown.
-For troubleshooting, you can forcefully enable Captchavel setting `enable_local` to `true`, or better, using your environment `.env` file and setting `CAPTCHAVEL_LOCAL` to `true`.
+### Threshold
```php
- env('CAPTCHAVEL_LOCAL', false),
+ 'threshold' => 0.4
];
```
-### Key and Secret
+Default threshold to check against reCAPTCHA v3 challenges. Values **equal or above** will be considered as human.
-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're not using reCAPTCHA v3, or you're fine with the default, leave this alone. You can still [override the default in a per-route basis](#threshold-action-and-input-name).
-If you don't have them, use the [Google reCAPTCHA Admin console](https://g.co/recaptcha/admin) to create a pair.
-
-### Threshold
-
-Google reCAPTCHA v3 returns a *score* for interactions. Lower scores means the Request has been probably made by a robot, while high scores mean a more human-like interaction.
-
-By default, this package uses a score of 0.5, which is considered *sane* in most of cases, but you can override it using the`CAPTCHAVEL_THRESHOLD` key with float values between 0.1 and 1.0.
-
-```dotenv
-CAPTCHAVEL_THRESHOLD=0.7
-```
-
-Aside from that, you can also override the score using a parameter within the `recaptha` middleware, which will take precedence over the default score (set or not). For example, you can set it lower for comments, but higher for product reviews.
+### Credentials
```php
- [
+ // ...
+ ]
+];
+```
-Route::post('{product}/comments')
- ->uses('Product/CommentController@create')
- ->middleware('recaptcha:0.3');
+Here is the full array of [reCAPTCHA credentials](#set-up) to use depending on the version. Do not change the array unless you know what you're doing.
-Route::post('{product}/review')
- ->uses('Product/ReviewController@create')
- ->middleware('recaptcha:0.8');
-```
+## Testing with Captchavel
-### Request Method
+When unit testing your application, this package [automatically fakes reCAPTCHA responses](#fake-responses) by setting.
-The Google reCAPTCHA library underneath allows to make the request to the reCAPTCHA servers using a custom "Request Method". The `request_method` key accepts the Class you want to instance.
+> When mocking requests, there is no need to add any reCAPTCHA token or secrets in your tests.
-The default `null` value is enough for any normal application, but you're free to, for example, create your own logic or use the classes included in the [ReCaptcha package](https://github.com/google/recaptcha/tree/master/src/ReCaptcha/RequestMethod) (that this package requires).
+When using reCAPTCHA v3 (score), you can fake a response made by a human or robot by simply using the `fakeHuman()` and `fakeRobot()` methods, which will score `1.0` or `0.0` respectively.
```php
'App\Http\ReCaptcha\GuzzleRequestMethod',
-];
-```
+use DarkGhostHunter\Captchavel\Facades\Captchavel;
-You can mimic this next example were we will use Guzzle.
+// Let the user login normally.
+Captchavel::fakeHuman();
-#### Example implementation
+$this->post('login', [
+ 'email' => 'test@test.com',
+ 'password' => '123456',
+])->assertRedirect('user.welcome');
-First, we will create our `GuzzleRequestMethod` with the `submit()` method as required. This method will return the reCAPTCHA response from the external server using the Guzzle Client.
+// ... but if it's a robot, force him to use 2FA.
+Captchavel::fakeRobot();
-`app\Http\ReCaptcha\GuzzleRequestMethod.php`
-```php
-post('login', [
+ 'email' => 'test@test.com',
+ 'password' => '123456',
+])->assertViewIs('login.2fa');
+```
-use ReCaptcha\RequestMethod;
-use ReCaptcha\RequestParameters;
+Alternatively, `fakeScore()` method that will fake any score you set.
-class GuzzleRequestMethod implements RequestMethod
-{
- // ...
-
- /**
- * Submit the request with the specified parameters.
- *
- * @param RequestParameters $params Request parameters
- * @return string Body of the reCAPTCHA response
- */
- public function submit(RequestParameters $params)
- {
- return (new \GuzzleHttp\Client)->post($params->toQueryString())
- ->getBody()
- ->getContents();
- }
-}
-```
+> Fake responses don't come with action, hostnames or APK package names.
-Then, we will add the class to the `request_method` key in our configuration:
+### Events
-`config/captchavel.php`
-```php
- 'App\Http\ReCaptcha\GuzzleRequestMethod',
-];
-```
+### Using your own reCAPTCHA middleware
-Finally, we will tell the Service Container to give our `GuzzleRequestMethod` to the underneath `ReCaptcha` class when Captchavel tries to instance it, using the Service Container [Contextual Binding](https://laravel.com/docs/container#contextual-binding).
+You may want to create your own reCAPTCHA middleware. Instead of doing one from scratch, you can extend the `BaseReCaptchaMiddleware`.
-`app\Providers\AppServiceProvider.php`
```php
app->when(ReCaptcha::class)
- ->needs(RequestMethod::class)
- ->give(function () {
- return new GuzzleRequestMethod;
- });
- }
-}
-```
+ $this->validateRequest($request, $input);
-We're leaving the Contextual Binding to you, since your *requester* may need some logic that a simple `app()->make(MyRequester::class)` may not be sufficient.
+ $response = $this->retrieve($request, $input, 2, 'checkbox');
-### Editing the Script view
-
-You can edit the script Blade view under by just creating a Blade template in `resources/vendor/captchavel/script.blade.php`.
-
-This blade view contains the reCAPTCHA script of the package. The view receives the `$key` variable witch is just the reCAPTCHA v3 Site Key.
-
-There you can edit how the script is downloaded from Google, and how it checks for forms to link with the backend, if the default script isn't enough for you.
-
-### AJAX Requests
+ if ($response->isInvalid()) {
+ throw $this->validationException($input, 'Complete the reCAPTCHA challenge');
+ }
-Depending on the application, AJAX Requests won't include the reCAPTCHA token. This may be for various reasons:
+ return $next($request);
+ }
+}
+```
-* Using virtual DOM frameworks like Vue and React.
-* Creating a form after the page loaded with JavaScript.
-* An AJAX Requests being done entirely in JavaScript.
+## Security
-In any of these scenarios, you may want disable the injection script and [use the reCAPATCHA v3 scripts directly](https://developers.google.com/recaptcha/docs/v3) or your [custom script](#editing-the-script-view).
+If you discover any security related issues, please email darkghosthunter@gmail.com instead of using the issue tracker.
## License
diff --git a/composer.json b/composer.json
index 0a6df2b..6f9bd17 100644
--- a/composer.json
+++ b/composer.json
@@ -1,57 +1,44 @@
{
"name": "darkghosthunter/captchavel",
- "description": "Easily integrate Google Recaptcha v3 into your Laravel application",
+ "description": "Integrate reCAPTCHA into your Laravel application better than the Big G itself!",
"keywords": [
"darkghosthunter",
- "captchavel",
- "laravel",
- "recaptcha",
- "google"
+ "recaptchavel",
+ "recaptcha"
],
- "minimum-stability": "dev",
- "prefer-stable": true,
"homepage": "https://github.com/darkghosthunter/captchavel",
"license": "MIT",
"type": "library",
"authors": [
{
- "name": "Italo Baeza Cabrera",
+ "name": "Italo Israel Baeza Cabrera",
"email": "darkghosthunter@gmail.com",
"role": "Developer"
}
],
"require": {
- "php": "^7.2.15",
- "ext-json" : "*",
- "google/recaptcha": "^1.2",
- "illuminate/config": "^6.0||^7.0",
- "illuminate/container": "^6.0||^7.0",
- "illuminate/http": "^6.0||^7.0",
- "illuminate/routing": "^6.0||^7.0",
- "illuminate/support": "^6.0||^7.0",
- "illuminate/validation": "^6.0||^7.0",
- "illuminate/view": "^6.0||^7.0"
+ "php": "^7.2",
+ "ext-json": "*",
+ "illuminate/support": "^7.0",
+ "guzzlehttp/guzzle": "^6.3.1||^7.0"
},
"require-dev": {
- "orchestra/testbench": "^4.1||^5.0"
+ "orchestra/testbench": "^5.0"
},
"autoload": {
- "files": [
- "src/helpers.php"
- ],
"psr-4": {
"DarkGhostHunter\\Captchavel\\": "src"
- }
+ },
+ "files": ["src/helpers.php"]
},
"autoload-dev": {
"psr-4": {
- "DarkGhostHunter\\Captchavel\\Tests\\": "tests"
+ "Tests\\": "tests"
}
},
"scripts": {
"test": "vendor/bin/phpunit",
"test-coverage": "vendor/bin/phpunit --coverage-html coverage"
-
},
"config": {
"sort-packages": true
@@ -62,7 +49,7 @@
"DarkGhostHunter\\Captchavel\\CaptchavelServiceProvider"
],
"aliases": {
- "ReCaptcha": "DarkGhostHunter\\Captchavel\\ReCaptcha"
+ "Captchavel": "DarkGhostHunter\\Captchavel\\Facades\\Captchavel"
}
}
}
diff --git a/config/captchavel.php b/config/captchavel.php
index 8662644..d1ed7c7 100644
--- a/config/captchavel.php
+++ b/config/captchavel.php
@@ -1,55 +1,57 @@
env('CAPTCHAVEL_MODE', 'auto'),
+ 'enable' => env('CAPTCHAVEL_ENABLE', false),
/*
|--------------------------------------------------------------------------
- | Enable on Local Environment
+ | Fake on local development
|--------------------------------------------------------------------------
|
- | Having reCAPTCHA on local environment is usually not a good idea unless
- | you want to make some manual-human tests. For these moments, you can
- | enable reCAPTCHA setting this to true until you are sure it works.
+ | Sometimes you may want to fake success or failed responses from reCAPTCHA
+ | servers in local development. To do this, simply enable the environment
+ | variable and then issue as a checkbox parameter is_robot to any form.
|
*/
- 'enable_local' => env('CAPTCHAVEL_LOCAL', false),
+ 'fake' => env('CAPTCHAVEL_FAKE', false),
/*
|--------------------------------------------------------------------------
- | Site Key and Secret
+ | Constraints
|--------------------------------------------------------------------------
|
- | Google reCAPTCHA issues two keys: a Site Key to show in your responses,
- | and a Secret you should hold privately, since this Secret checks the
- | reCAPTCHA behaviour. Check the reCAPTCHA Admin panel to make them.
+ | These default constraints allows further verification of the incoming
+ | response from reCAPTCHA servers. Hostname and APK Package Name are
+ | required if these are not verified in your reCAPTCHA admin panel.
|
*/
- 'key' => env('RECAPTCHA_V3_KEY'),
- 'secret' => env('RECAPTCHA_V3_SECRET'),
+ 'hostname' => env('RECAPTCHA_HOSTNAME'),
+ 'apk_package_name' => env('RECAPTCHA_APK_PACKAGE_NAME'),
/*
|--------------------------------------------------------------------------
| Threshold
|--------------------------------------------------------------------------
|
- | The response from reCAPTCHA contains a score of interactivity. You can
- | set the default threshold number to differentiate between humans and
- | robots, so you can make actions depending on who made the Request.
+ | For reCAPTCHA v3, which is an score-driven interaction, this default
+ | threshold is the slicing point between bots and humans. If a score
+ | is below this threshold, it means the request was made by a bot.
|
*/
@@ -57,14 +59,33 @@
/*
|--------------------------------------------------------------------------
- | Request Method
+ | Credenctials
|--------------------------------------------------------------------------
|
- | The underlying Google reCAPTCHA library for PHP admits a custom Request
- | Method for your application. That means, you can delegate an specific
- | class to handle how to send and to receive the reCaptcha response.
+ | The following is the array of credentials for each version and variant
+ | of the reCAPTCHA services. You shouldn't need to edit this unless you
+ | know what you're doing. On reCAPTCHA v2, it comes with testing keys.
|
*/
- 'request_method' => null,
-];
\ No newline at end of file
+ 'credentials' => [
+ 'v2' => [
+ 'checkbox' => [
+ 'secret' => env('RECAPTCHA_V2_CHECKBOX_SECRET', Captchavel::TEST_V2_SECRET),
+ 'key' => env('RECAPTCHA_V2_CHECKBOX_KEY', Captchavel::TEST_V2_KEY),
+ ],
+ 'invisible' => [
+ 'secret' => env('RECAPTCHA_V2_INVISIBLE_SECRET', Captchavel::TEST_V2_SECRET),
+ 'key' => env('RECAPTCHA_V2_INVISIBLE_KEY', Captchavel::TEST_V2_KEY),
+ ],
+ 'android' => [
+ 'secret' => env('RECAPTCHA_V2_ANDROID_SECRET', Captchavel::TEST_V2_SECRET),
+ 'key' => env('RECAPTCHA_V2_ANDROID_KEY', Captchavel::TEST_V2_KEY),
+ ],
+ ],
+ 'v3' => [
+ 'secret' => env('RECAPTCHA_V3_SECRET'),
+ 'key' => env('RECAPTCHA_V3_KEY'),
+ ],
+ ],
+];
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index aba488b..12ba420 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -19,10 +19,6 @@
src/
-
-
-
-
diff --git a/resources/views/script.blade.php b/resources/views/script.blade.php
deleted file mode 100644
index 741b30a..0000000
--- a/resources/views/script.blade.php
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
diff --git a/src/Captchavel.php b/src/Captchavel.php
new file mode 100644
index 0000000..94298bd
--- /dev/null
+++ b/src/Captchavel.php
@@ -0,0 +1,166 @@
+httpFactory = $httpFactory;
+ $this->config = $config;
+ }
+
+ /**
+ * Returns the Captchavel Response, if any.
+ *
+ * @return null|\DarkGhostHunter\Captchavel\Http\ReCaptchaResponse
+ */
+ public function getResponse()
+ {
+ return $this->response;
+ }
+
+ /**
+ * Check if the a response was resolved from reCAPTCHA servers.
+ *
+ * @return bool
+ */
+ public function isNotResolved()
+ {
+ return $this->response === null;
+ }
+
+ /**
+ * Sets the correct credentials to use to retrieve the challenge results.
+ *
+ * @param int $version
+ * @param string|null $variant
+ * @return $this
+ */
+ public function useCredentials(int $version, string $variant = null)
+ {
+ if ($version === 2) {
+ if (! in_array($variant, static::V2_VARIANTS, true)) {
+ throw new LogicException("The reCAPTCHA v2 variant must be [checkbox], [invisible] or [android].");
+ }
+ $this->secret = $this->config->get("captchavel.credentials.v2.{$variant}.secret");
+ } elseif ($version === 3) {
+ $this->secret = $this->config->get('captchavel.credentials.v3.secret');
+ }
+
+ if (! $this->secret) {
+ $name = 'v' . $version . ($variant ? '-' . $variant : '');
+ throw new LogicException("The reCAPTCHA secret for [{$name}] doesn't exists.");
+ }
+
+ return $this;
+ }
+
+ /**
+ * Retrieves the Response Challenge.
+ *
+ * @param string $challenge
+ * @param string $ip
+ * @return \DarkGhostHunter\Captchavel\Http\ReCaptchaResponse
+ */
+ public function retrieve(string $challenge, string $ip)
+ {
+ $response = $this->httpFactory->asForm()
+ ->withOptions(['version' => 2.0])
+ ->post(static::RECAPTCHA_ENDPOINT, [
+ 'secret' => $this->secret,
+ 'response' => $challenge,
+ 'remoteip' => $ip,
+ ]);
+
+ return $this->parse($response);
+ }
+
+ /**
+ * Parses the Response
+ *
+ * @param \Illuminate\Http\Client\Response $response
+ * @return \DarkGhostHunter\Captchavel\Http\ReCaptchaResponse
+ */
+ protected function parse(Response $response)
+ {
+ return $this->response = new ReCaptchaResponse($response->json());
+ }
+}
diff --git a/src/CaptchavelFake.php b/src/CaptchavelFake.php
new file mode 100644
index 0000000..eae39dc
--- /dev/null
+++ b/src/CaptchavelFake.php
@@ -0,0 +1,82 @@
+response = $response;
+
+ return $this;
+ }
+
+ /**
+ * Sets the correct credentials to use to retrieve the challenge results.
+ *
+ * @param int $version
+ * @param string|null $variant
+ * @return $this
+ */
+ public function useCredentials(int $version, string $variant = null)
+ {
+ return $this;
+ }
+
+ /**
+ * Retrieves the Response Challenge.
+ *
+ * @param string $challenge
+ * @param string $ip
+ * @return \DarkGhostHunter\Captchavel\Http\ReCaptchaResponse
+ */
+ public function retrieve(?string $challenge, string $ip)
+ {
+ return $this->response;
+ }
+
+ /**
+ * Makes the fake Captchavel response with a fake score.
+ *
+ * @param float $score
+ * @return $this
+ */
+ public function fakeScore(float $score)
+ {
+ return $this->setResponse(new ReCaptchaResponse([
+ 'success' => true,
+ 'score' => $score,
+ 'action' => null,
+ 'hostname' => null,
+ 'apk_package_name' => null,
+ ]));
+ }
+
+ /**
+ * Makes a fake Captchavel response made by a robot with "0" score.
+ *
+ * @return $this
+ */
+ public function fakeRobot()
+ {
+ return $this->fakeScore(0);
+ }
+
+ /**
+ * Makes a fake Captchavel response made by a human with "1.0" score.
+ *
+ * @return $this
+ */
+ public function fakeHuman()
+ {
+ return $this->fakeScore(1);
+ }
+}
diff --git a/src/CaptchavelServiceProvider.php b/src/CaptchavelServiceProvider.php
index 10b1bac..17e673c 100644
--- a/src/CaptchavelServiceProvider.php
+++ b/src/CaptchavelServiceProvider.php
@@ -2,14 +2,11 @@
namespace DarkGhostHunter\Captchavel;
-use Illuminate\Http\Request;
use Illuminate\Routing\Router;
-use Illuminate\Contracts\Http\Kernel;
use Illuminate\Support\ServiceProvider;
-use ReCaptcha\ReCaptcha as ReCaptchaFactory;
-use DarkGhostHunter\Captchavel\Http\Middleware\CheckRecaptcha;
-use DarkGhostHunter\Captchavel\Http\Middleware\TransparentRecaptcha;
-use DarkGhostHunter\Captchavel\Http\Middleware\InjectRecaptchaScript;
+use Illuminate\Contracts\Config\Repository;
+use DarkGhostHunter\Captchavel\Http\Middleware\VerifyReCaptchaV2;
+use DarkGhostHunter\Captchavel\Http\Middleware\VerifyReCaptchaV3;
class CaptchavelServiceProvider extends ServiceProvider
{
@@ -20,117 +17,31 @@ class CaptchavelServiceProvider extends ServiceProvider
*/
public function register()
{
- // Automatically apply the package configuration
- $this->mergeConfigFrom(__DIR__ . '/../config/captchavel.php', 'captchavel');
+ $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::class);
- $this->app->alias(ReCaptcha::class, 'recaptcha');
+ $this->app->singleton(Captchavel::class);
}
/**
* Bootstrap the application services.
*
- * @throws \Illuminate\Contracts\Container\BindingResolutionException
+ * @param \Illuminate\Routing\Router $router
+ * @param \Illuminate\Contracts\Config\Repository $config
+ * @return void
*/
- public function boot()
+ public function boot(Router $router, Repository $config)
{
- $this->loadViewsFrom(__DIR__ . '/../resources/views', 'captchavel');
-
if ($this->app->runningInConsole()) {
$this->publishes([
- __DIR__ . '/../config/captchavel.php' => config_path('captchavel.php'),
+ __DIR__.'/../config/config.php' => config_path('captchavel.php'),
], 'config');
- // Publishing the views.
- $this->publishes([
- __DIR__ . '/../resources/views' => resource_path('views/vendor/captchavel'),
- ], 'views');
- }
-
- $this->bootMiddleware();
- $this->extendRequestMacro();
- }
-
- /**
- * Registers the Middleware
- *
- * @return void
- * @throws \Illuminate\Contracts\Container\BindingResolutionException
- */
- protected function bootMiddleware()
- {
- /** @var \Illuminate\Routing\Router $router */
- $router = $this->app->make('router');
-
- // We will check if we should enable the Middleware of this package based on the environment
- // 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->addMiddleware($router);
- } else {
- $this->addTransparentMiddleware($router);
- }
- }
-
- /**
- * Returns if the application should enable ReCaptcha middleware
- *
- * @return bool
- * @throws \Illuminate\Contracts\Container\BindingResolutionException
- */
- protected function shouldEnableMiddleware()
- {
- return $this->app->environment('production')
- || ($this->app->environment('local') && $this->app->make('config')->get('captchavel.enable_local'));
- }
-
- /**
- * Registers real middleware for the package
- *
- * @param \Illuminate\Routing\Router $router
- * @throws \Illuminate\Contracts\Container\BindingResolutionException
- */
- protected function addMiddleware(Router $router)
- {
- $router->aliasMiddleware('recaptcha', CheckRecaptcha::class);
- $router->aliasMiddleware('recaptcha-inject', InjectRecaptchaScript::class);
-
- if ($this->app->make('config')->get('captchavel.mode') === 'auto') {
- $this->app->make(Kernel::class)->pushMiddleware(InjectRecaptchaScript::class);
+ if ($this->app->runningUnitTests()) {
+ $config->set('captchavel.fake', true);
+ }
}
- }
- /**
- * Registers a Dummy (Transparent) Middleware
- *
- * @param \Illuminate\Routing\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();
- });
+ $router->aliasMiddleware('recaptcha.v2', VerifyReCaptchaV2::class);
+ $router->aliasMiddleware('recaptcha.v3', VerifyReCaptchaV3::class);
}
-
}
diff --git a/src/Events/ReCaptchaResponseReceived.php b/src/Events/ReCaptchaResponseReceived.php
new file mode 100644
index 0000000..b64c440
--- /dev/null
+++ b/src/Events/ReCaptchaResponseReceived.php
@@ -0,0 +1,35 @@
+request = $request;
+ $this->response = $response;
+ }
+}
diff --git a/src/Exceptions/CaptchavelException.php b/src/Exceptions/CaptchavelException.php
deleted file mode 100644
index 274f665..0000000
--- a/src/Exceptions/CaptchavelException.php
+++ /dev/null
@@ -1,7 +0,0 @@
-message = 'The Google reCAPTCHA library returned the following errors:' .
- implode("\n- ", $errorCodes);
-
- parent::__construct();
- }
-}
\ No newline at end of file
diff --git a/src/Exceptions/InvalidCaptchavelMiddlewareMethod.php b/src/Exceptions/InvalidCaptchavelMiddlewareMethod.php
deleted file mode 100644
index 63e1603..0000000
--- a/src/Exceptions/InvalidCaptchavelMiddlewareMethod.php
+++ /dev/null
@@ -1,15 +0,0 @@
-message = 'The reCAPTCHA token received is invalid';
- }
-
- parent::__construct($this->message, $code, $previous);
- }
-}
\ No newline at end of file
diff --git a/src/Exceptions/RecaptchaNotResolvedException.php b/src/Exceptions/RecaptchaNotResolvedException.php
deleted file mode 100644
index a4d8195..0000000
--- a/src/Exceptions/RecaptchaNotResolvedException.php
+++ /dev/null
@@ -1,15 +0,0 @@
-make(CaptchavelFake::class));
+
+ return $fake;
+ }
+
+ /**
+ * Makes the fake Captchavel response with a fake score.
+ *
+ * @param float $score
+ * @return \DarkGhostHunter\Captchavel\CaptchavelFake
+ */
+ public static function fakeScore(float $score)
+ {
+ return static::fake()->fakeScore($score);
+ }
+
+ /**
+ * Makes a fake Captchavel response made by a robot with "0" score.
+ *
+ * @return \DarkGhostHunter\Captchavel\CaptchavelFake
+ */
+ public static function fakeRobot()
+ {
+ return static::fake()->fakeRobot();
+ }
+
+ /**
+ * Makes a fake Captchavel response made by a human with "1.0" score.
+ *
+ * @return \DarkGhostHunter\Captchavel\CaptchavelFake
+ */
+ public static function fakeHuman()
+ {
+ return static::fake()->fakeHuman();
+ }
+}
diff --git a/src/Facades/ReCaptcha.php b/src/Facades/ReCaptcha.php
deleted file mode 100644
index 9a275c4..0000000
--- a/src/Facades/ReCaptcha.php
+++ /dev/null
@@ -1,29 +0,0 @@
-captchavel = $captchavel;
+ $this->config = $config;
+ }
+
+ /**
+ * Determines if the reCAPTCHA verification should be enabled.
+ *
+ * @return bool
+ */
+ protected function isEnabled()
+ {
+ return $this->config->get('captchavel.enable');
+ }
+
+ /**
+ * Check if the reCAPTCHA response can be faked on-demand.
+ *
+ * @return bool
+ */
+ protected function isFake()
+ {
+ return $this->config->get('captchavel.fake');
+ }
+
+ /**
+ * Check if the reCAPTCHA response must be real.
+ *
+ * @return bool
+ */
+ protected function isReal()
+ {
+ return ! $this->isFake();
+ }
+
+ /**
+ * Validate if this Request has the reCAPTCHA challenge string.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param string $name
+ * @return void
+ * @throws \Illuminate\Validation\ValidationException
+ */
+ protected function validateRequest($request, string $name)
+ {
+ if (! is_string($request->get($name))) {
+ throw $this->validationException($name, 'The reCAPTCHA challenge is missing or has not been completed.');
+ }
+ }
+
+ /**
+ * Retrieves the Captchavel response from reCAPTCHA servers.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param string $input
+ * @param int $version
+ * @param string|null $variant
+ * @return \DarkGhostHunter\Captchavel\Http\ReCaptchaResponse
+ */
+ protected function retrieve(Request $request, string $input, int $version, string $variant = null)
+ {
+ return $this->captchavel
+ ->useCredentials($version, $variant)
+ ->retrieve($request->input($input), $request->ip());
+ }
+
+ /**
+ * Fakes a v3 reCAPTCHA response.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @return void
+ */
+ protected function fakeResponseScore($request)
+ {
+ // We will first check if the Captchavel instance was not already faked. If it has been,
+ // we will hands out the control to the developer. Otherwise, we will manually create a
+ // fake Captchavel instance and look for the "is_robot" parameter to set a fake score.
+ if ($this->captchavel instanceof Captchavel && $this->captchavel->isNotResolved()) {
+ $this->captchavel = $request->has('is_robot') ?
+ CaptchavelFacade::fakeRobot() :
+ CaptchavelFacade::fakeHuman();
+ }
+ }
+
+ /**
+ * Validate the Hostname and APK name from the response.
+ *
+ * @param \DarkGhostHunter\Captchavel\Http\ReCaptchaResponse $response
+ * @param string $input
+ * @param string|null $action
+ * @throws \Illuminate\Validation\ValidationException
+ */
+ protected function validateResponse(ReCaptchaResponse $response, $input = Captchavel::INPUT, ?string $action = null)
+ {
+ if ($response->differentHostname($this->config->get('captchavel.hostname'))) {
+ throw $this->validationException('hostname',
+ "The hostname [{$response->hostname}] of the response is invalid.");
+ }
+
+ if ($response->differentApk($this->config->get('captchavel.apk_package_name'))) {
+ throw $this->validationException('apk_package_name',
+ "The apk_package_name [{$response->apk_package_name}] of the response is invalid.");
+ }
+
+ if ($response->differentAction($action)) {
+ throw $this->validationException('action',
+ "The action [{$response->action}] of the response is invalid.");
+ }
+
+ if ($response->isInvalid()) {
+ throw $this->validationException($input,
+ "The reCAPTCHA challenge is invalid or was not completed.");
+ }
+ }
+
+ /**
+ * Creates a new Validation Exception instance.
+ *
+ * @param string $input
+ * @param string $message
+ * @return \Illuminate\Validation\ValidationException
+ */
+ protected function validationException($input, $message)
+ {
+ return ValidationException::withMessages([$input => trans($message)])->redirectTo(back()->getTargetUrl());
+ }
+
+ /**
+ * Dispatch an event with the request and the Captchavel Response
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param \DarkGhostHunter\Captchavel\Http\ReCaptchaResponse $response
+ */
+ protected function dispatch(Request $request, ReCaptchaResponse $response)
+ {
+ event(new ReCaptchaResponseReceived($request, $response));
+ }
+}
diff --git a/src/Http/Middleware/CheckRecaptcha.php b/src/Http/Middleware/CheckRecaptcha.php
deleted file mode 100644
index dadaa12..0000000
--- a/src/Http/Middleware/CheckRecaptcha.php
+++ /dev/null
@@ -1,139 +0,0 @@
-validator = $validator;
- $this->config = $config->get('captchavel');
- $this->reCaptchaFactory = $reCaptchaFactory;
- $this->reCaptcha = $reCaptcha;
- }
-
- /**
- * Handle an incoming request.
- *
- * @param \Illuminate\Http\Request $request
- * @param \Closure $next
- * @param float $threshold
- * @return mixed
- * @throws \Throwable
- */
- public function handle($request, Closure $next, float $threshold = null)
- {
- if ($request->getRealMethod() === 'POST') {
- $this->hasValidRequest($request);
- $this->hasValidReCaptcha($request, $threshold ?? $this->config['threshold']);
- }
-
- return $next($request);
- }
-
- /**
- * Return if the Request has a valid reCAPTCHA token
- *
- * @param \Illuminate\Http\Request $request
- * @return bool
- * @throws \Throwable
- */
- protected function hasValidRequest(Request $request)
- {
- $isValid = !$this->validator->make($request->only('_recaptcha'), [
- '_recaptcha' => 'required|string',
- ])->fails();
-
- return throw_unless($isValid, InvalidRecaptchaException::class, $request->only('_recaptcha'));
- }
-
- /**
- * Checks if the reCAPTCHA Response is valid
- *
- * @param \Illuminate\Http\Request $request
- * @param float $threshold
- * @return mixed
- * @throws \Throwable
- */
- protected function hasValidReCaptcha(Request $request, float $threshold)
- {
- $response = $this->resolve($request, $threshold)->response();
-
- return throw_unless($response->isSuccess(), FailedRecaptchaException::class, $response->getErrorCodes());
- }
-
- /**
- * Resolves a reCAPTCHA Request into a reCAPTCHA Response
- *
- * @param \Illuminate\Http\Request $request
- * @param float $threshold
- * @return \DarkGhostHunter\Captchavel\ReCaptcha
- */
- protected function resolve(Request $request, float $threshold)
- {
- return $this->reCaptcha->setResponse(
- $this->reCaptchaFactory
- ->setExpectedAction($this->sanitizeAction($request->getRequestUri()))
- ->verify($request->input('_recaptcha'), $request->getClientIp())
- )->setThreshold($threshold);
- }
-
- /**
- * Sanitizes the Action string to be sent to Google reCAPTCHA servers
- *
- * @param string $action
- * @return string|string[]|null
- */
- protected function sanitizeAction(string $action)
- {
- return preg_replace('/[^A-z\/\_]/s', '', $action);
- }
-}
diff --git a/src/Http/Middleware/InjectRecaptchaScript.php b/src/Http/Middleware/InjectRecaptchaScript.php
deleted file mode 100644
index 3d91ec9..0000000
--- a/src/Http/Middleware/InjectRecaptchaScript.php
+++ /dev/null
@@ -1,96 +0,0 @@
-key = $config->get('captchavel.key');
- $this->view = $view;
- }
-
- /**
- * Handle an incoming request.
- *
- * @param \Illuminate\Http\Request $request
- * @param \Closure $next
- * @return mixed
- * @throws \Throwable
- * @throws \\Symfony\Component\HttpKernel\Exception\HttpException
- */
- public function handle($request, Closure $next)
- {
- $response = $next($request);
-
- if ($this->isHtml($request, $response)) {
- return $this->injectScript($response);
- }
-
- return $response;
- }
-
- /**
- * Detect if the Request accepts HTML and is not an AJAX/PJAX Request
- *
- * @param \Illuminate\Http\Request $request
- * @param \Illuminate\Http\Response | \Illuminate\Http\JsonResponse $response
- * @return bool
- */
- protected function isHtml(Request $request, $response)
- {
- return $response instanceof Response
- && $request->acceptsHtml()
- && ! $request->ajax()
- && ! $request->pjax()
- && ! $response->exception;
- }
-
- /**
- * Injects the front-end Scripts
- *
- * @param \Illuminate\Http\Response $response
- * @return \Illuminate\Http\Response
- */
- protected function injectScript(Response $response)
- {
- // To inject the script automatically, we will do it before the ending
- // head tag. If it's not found, the response may not be valid HTML,
- // so we will bail out returning the original untouched content.
- if (! $endHeadPosition = stripos($content = $response->content(), '')) {
- return $response;
- };
-
- $script = $this->view->make('captchavel::script', ['key' => $this->key])->render();
-
- return $response->setContent(
- substr_replace($content, $script, $endHeadPosition, 0)
- );
- }
-}
\ No newline at end of file
diff --git a/src/Http/Middleware/TransparentRecaptcha.php b/src/Http/Middleware/TransparentRecaptcha.php
deleted file mode 100644
index fbdd525..0000000
--- a/src/Http/Middleware/TransparentRecaptcha.php
+++ /dev/null
@@ -1,49 +0,0 @@
-validator->make($request->only('_recaptcha'), [
- '_recaptcha' => 'nullable',
- ])->fails();
-
- return throw_unless($isValid, InvalidRecaptchaException::class, $request->only('_recaptcha'));
- }
- /**
- * Resolves a reCAPTCHA Request into a reCAPTCHA Response
- *
- * @param \Illuminate\Http\Request $request
- * @param float $threshold
- * @return \DarkGhostHunter\Captchavel\ReCaptcha
- */
- protected function resolve(Request $request, float $threshold)
- {
- return $this->reCaptcha->setResponse(
- new Response(
- true,
- [],
- null,
- now()->toIso8601ZuluString(),
- null,
- (int)$request->has('is_robot'),
- $this->sanitizeAction($request->getRequestUri()))
- );
- }
-}
diff --git a/src/Http/Middleware/VerifyReCaptchaV2.php b/src/Http/Middleware/VerifyReCaptchaV2.php
new file mode 100644
index 0000000..6990908
--- /dev/null
+++ b/src/Http/Middleware/VerifyReCaptchaV2.php
@@ -0,0 +1,44 @@
+isEnabled() && $this->isReal()) {
+ $this->validateRequest($request, $input);
+ $this->processChallenge($request, $variant, $input);
+ }
+
+ return $next($request);
+ }
+
+ /**
+ * Process a real challenge and response from reCAPTCHA servers.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param string $variant
+ * @param string $input
+ * @throws \Illuminate\Validation\ValidationException
+ */
+ protected function processChallenge($request, $variant, $input)
+ {
+ $this->dispatch($request, $response = $this->retrieve($request, $input, 2, $variant));
+
+ $this->validateResponse($response, $input);
+ }
+}
diff --git a/src/Http/Middleware/VerifyReCaptchaV3.php b/src/Http/Middleware/VerifyReCaptchaV3.php
new file mode 100644
index 0000000..ad660a1
--- /dev/null
+++ b/src/Http/Middleware/VerifyReCaptchaV3.php
@@ -0,0 +1,86 @@
+isEnabled()) {
+ if ($this->isReal()) {
+ $this->validateRequest($request, $input);
+ } else {
+ $this->fakeResponseScore($request);
+
+ // We will disable the action name since it will be verified if we don't null it.
+ $action = null;
+ }
+
+ $this->processChallenge($request, $threshold, $action, $input);
+ }
+
+ return $next($request);
+ }
+
+ /**
+ * Process the response from reCAPTCHA servers.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param null|string $threshold
+ * @param null|string $action
+ * @param string $input
+ * @throws \Illuminate\Validation\ValidationException
+ */
+ protected function processChallenge($request, $threshold, $action, $input)
+ {
+ $response = $this->retrieve($request, $input, 3);
+
+ $response->setThreshold($this->normalizeThreshold($threshold));
+
+ $this->dispatch($request, $response);
+
+ $this->validateResponse($response, $input, $this->normalizeAction($action));
+
+ // After we get the response, we will register the instance as a shared ("singleton").
+ // Obviously we will set the threshold set by the developer or just use the default.
+ // The Response should not be available until the middleware runs, so this is ok.
+ app()->instance(ReCaptchaResponse::class, $response);
+ }
+
+ /**
+ * Normalize the threshold string.
+ *
+ * @param string|null $threshold
+ * @return array|float|mixed
+ */
+ protected function normalizeThreshold($threshold)
+ {
+ return $threshold === 'null' ? $this->config->get('captchavel.threshold') : (float)$threshold;
+ }
+
+ /**
+ * Normalizes the action name, or returns null.
+ *
+ * @param null|string $action
+ * @return null|string
+ */
+ protected function normalizeAction($action)
+ {
+ return strtolower($action) === 'null' ? null : $action;
+ }
+}
diff --git a/src/Http/ReCaptchaResponse.php b/src/Http/ReCaptchaResponse.php
new file mode 100644
index 0000000..6661154
--- /dev/null
+++ b/src/Http/ReCaptchaResponse.php
@@ -0,0 +1,132 @@
+threshold = $threshold;
+
+ return $this;
+ }
+
+ /**
+ * Returns if the response was made by a Human.
+ *
+ * @throws \LogicException
+ * @return bool
+ */
+ public function isHuman()
+ {
+ if ($this->score === null) {
+ throw new LogicException('This is not a reCAPTCHA v3 response, or the score is absent.');
+ }
+
+ return $this->threshold >= $this->score;
+ }
+
+ /**
+ * Returns if the response was made by a Robot.
+ *
+ * @return bool
+ */
+ public function isRobot()
+ {
+ return ! $this->isHuman();
+ }
+
+ /**
+ * Returns if the challenge is valid.
+ *
+ * @return bool
+ */
+ public function isValid()
+ {
+ return $this->success && empty($this->error_codes);
+ }
+
+ /**
+ * Returns if the challenge is invalid.
+ *
+ * @return bool
+ */
+ public function isInvalid()
+ {
+ return ! $this->isValid();
+ }
+
+ /**
+ * Check if the hostname is different to the one issued.
+ *
+ * @param string|null $string
+ * @return bool
+ */
+ public function differentHostname(?string $string)
+ {
+ return $string && $this->hostname !== $string;
+ }
+
+ /**
+ * Check if the APK name is different to the one issued.
+ *
+ * @param string|null $string
+ * @return bool
+ */
+ public function differentApk(?string $string)
+ {
+ return $string && $this->apk_package_name !== $string;
+ }
+
+ /**
+ * Check if the action name is different to the one issued.
+ *
+ * @param null|string $action
+ * @return bool
+ */
+ public function differentAction(?string $action)
+ {
+ return $action && $this->action !== $action;
+ }
+
+ /**
+ * Dynamically return an attribute as a property.
+ *
+ * @param $name
+ * @return null|mixed
+ */
+ public function __get($name)
+ {
+ // Minor fix for getting the error codes
+ return parent::__get($name === 'error_codes' ? 'error-codes' : $name);
+ }
+}
diff --git a/src/ReCaptcha.php b/src/ReCaptcha.php
deleted file mode 100644
index 14a3cf7..0000000
--- a/src/ReCaptcha.php
+++ /dev/null
@@ -1,128 +0,0 @@
-response = $response;
-
- return $this;
- }
-
- /**
- * Returns if the reCAPTCHA has been resolved by the servers
- *
- * @return bool
- */
- public function isResolved()
- {
- return $this->response !== null;
- }
-
- /**
- * Returns the threshold
- *
- * @return float
- */
- public function getThreshold()
- {
- return $this->threshold;
- }
-
- /**
- * Sets the Threshold
- *
- * @param float $threshold
- * @return \DarkGhostHunter\Captchavel\ReCaptcha
- */
- public function setThreshold(float $threshold)
- {
- $this->threshold = $threshold;
-
- return $this;
- }
-
- /**
- * 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()
- {
- return !$this->isHuman();
- }
-
- /**
- * Return the underlying reCAPTCHA response
- *
- * @return \ReCaptcha\Response
- */
- public function response()
- {
- return $this->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());
- }
-}
diff --git a/src/helpers.php b/src/helpers.php
index cd34df1..3f6c4c5 100644
--- a/src/helpers.php
+++ b/src/helpers.php
@@ -1,13 +1,23 @@
afterApplicationCreated(function () {
+ $this->createsRoutes();
+ });
+
+ parent::setUp();
+ }
+
+ public function test_using_fake_on_unit_test()
+ {
+ $this->assertTrue(config('captchavel.fake'));
+ }
+
+ public function test_makes_fake_score()
+ {
+ Captchavel::fakeScore(0.3);
+
+ Route::post('test', function (ReCaptchaResponse $response) {
+ return [$response->score, $response->isRobot(), $response->isHuman()];
+ })->middleware('recaptcha.v3:0.6');
+
+ $this->post('test')->assertOk()->assertExactJson([0.3, true, false]);
+ }
+
+ public function test_makes_human_score_one()
+ {
+ Captchavel::fakeHuman();
+
+ Route::post('test', function (ReCaptchaResponse $response) {
+ return [$response->score, $response->isRobot(), $response->isHuman()];
+ })->middleware('recaptcha.v3:0.6');
+
+ $this->post('test')->assertOk()->assertExactJson([1.0, false, true]);
+ }
+
+ public function test_makes_robot_score_zero()
+ {
+ Captchavel::fakeRobot();
+
+ Route::post('test', function (ReCaptchaResponse $response) {
+ return [$response->score, $response->isRobot(), $response->isHuman()];
+ })->middleware('recaptcha.v3:0.6');
+
+ $this->post('test')->assertOk()->assertExactJson([0.0, true, false]);
+ }
+}
diff --git a/tests/CaptchavelTest.php b/tests/CaptchavelTest.php
new file mode 100644
index 0000000..5aa9d31
--- /dev/null
+++ b/tests/CaptchavelTest.php
@@ -0,0 +1,193 @@
+mock(Factory::class);
+
+ $mock->shouldReceive('asForm')->withNoArgs()->times(3)->andReturnSelf();
+ $mock->shouldReceive('withOptions')->with(['version' => 2.0])->times(3)->andReturnSelf();
+ $mock->shouldReceive('post')
+ ->with(Captchavel::RECAPTCHA_ENDPOINT, [
+ 'secret' => Captchavel::TEST_V2_SECRET,
+ 'response' => 'token',
+ 'remoteip' => '127.0.0.1',
+ ])
+ ->times(3)
+ ->andReturn(new Response(new GuzzleResponse(200, ['Content-type' => 'application/json'], json_encode([
+ 'success' => true,
+ 'score' => 0.5,
+ 'foo' => 'bar',
+ ]))));
+
+ $instance = app(Captchavel::class);
+
+ $this->assertNull($instance->getResponse());
+
+ $checkbox = $instance->useCredentials(2, 'checkbox')->retrieve('token', '127.0.0.1');
+ $this->assertSame($checkbox, $instance->getResponse());
+ $this->assertInstanceOf(ReCaptchaResponse::class, $checkbox);
+ $this->assertTrue($checkbox->success);
+ $this->assertSame(0.5, $checkbox->score);
+ $this->assertSame('bar', $checkbox->foo);
+
+ $invisible = $instance->useCredentials(2, 'invisible')->retrieve('token', '127.0.0.1');
+ $this->assertSame($invisible, $instance->getResponse());
+ $this->assertInstanceOf(ReCaptchaResponse::class, $invisible);
+ $this->assertTrue($invisible->success);
+ $this->assertSame(0.5, $invisible->score);
+ $this->assertSame('bar', $invisible->foo);
+
+ $android = $instance->useCredentials(2, 'android')->retrieve('token', '127.0.0.1');
+ $this->assertSame($android, $instance->getResponse());
+ $this->assertInstanceOf(ReCaptchaResponse::class, $android);
+ $this->assertTrue($android->success);
+ $this->assertSame(0.5, $android->score);
+ $this->assertSame('bar', $android->foo);
+ }
+
+ public function test_uses_v2_custom_credentials()
+ {
+ config(['captchavel.credentials.v2' => [
+ 'checkbox' => ['secret' => 'secret-checkbox'],
+ 'invisible' => ['secret' => 'secret-invisible'],
+ 'android' => ['secret' => 'secret-android'],
+ ]]);
+
+ $mock = $this->mock(Factory::class);
+
+ $mock->shouldReceive('asForm')->withNoArgs()->times(3)->andReturnSelf();
+ $mock->shouldReceive('withOptions')->with(['version' => 2.0])->times(3)->andReturnSelf();
+
+ $mock->shouldReceive('post')
+ ->with(Captchavel::RECAPTCHA_ENDPOINT, [
+ 'secret' => 'secret-checkbox',
+ 'response' => 'token',
+ 'remoteip' => '127.0.0.1',
+ ])
+ ->once()
+ ->andReturn(new Response(new GuzzleResponse(200, ['Content-type' => 'application/json'], json_encode([
+ 'success' => true,
+ 'score' => 0.5,
+ 'foo' => 'bar',
+ ]))));
+
+ $mock->shouldReceive('post')
+ ->with(Captchavel::RECAPTCHA_ENDPOINT, [
+ 'secret' => 'secret-invisible',
+ 'response' => 'token',
+ 'remoteip' => '127.0.0.1',
+ ])
+ ->once()
+ ->andReturn(new Response(new GuzzleResponse(200, ['Content-type' => 'application/json'], json_encode([
+ 'success' => true,
+ 'score' => 0.5,
+ 'foo' => 'bar',
+ ]))));
+
+ $mock->shouldReceive('post')
+ ->with(Captchavel::RECAPTCHA_ENDPOINT, [
+ 'secret' => 'secret-android',
+ 'response' => 'token',
+ 'remoteip' => '127.0.0.1',
+ ])
+ ->once()
+ ->andReturn(new Response(new GuzzleResponse(200, ['Content-type' => 'application/json'], json_encode([
+ 'success' => true,
+ 'score' => 0.5,
+ 'foo' => 'bar',
+ ]))));
+
+ $instance = app(Captchavel::class);
+
+ $this->assertNull($instance->getResponse());
+
+ $checkbox = $instance->useCredentials(2, 'checkbox')->retrieve('token', '127.0.0.1');
+ $this->assertSame($checkbox, $instance->getResponse());
+ $this->assertInstanceOf(ReCaptchaResponse::class, $checkbox);
+ $this->assertTrue($checkbox->success);
+ $this->assertSame(0.5, $checkbox->score);
+ $this->assertSame('bar', $checkbox->foo);
+
+ $invisible = $instance->useCredentials(2, 'invisible')->retrieve('token', '127.0.0.1');
+ $this->assertSame($invisible, $instance->getResponse());
+ $this->assertInstanceOf(ReCaptchaResponse::class, $invisible);
+ $this->assertTrue($invisible->success);
+ $this->assertSame(0.5, $invisible->score);
+ $this->assertSame('bar', $invisible->foo);
+
+ $android = $instance->useCredentials(2, 'android')->retrieve('token', '127.0.0.1');
+ $this->assertSame($android, $instance->getResponse());
+ $this->assertInstanceOf(ReCaptchaResponse::class, $android);
+ $this->assertTrue($android->success);
+ $this->assertSame(0.5, $android->score);
+ $this->assertSame('bar', $android->foo);
+ }
+
+ public function test_exception_if_no_v3_secret_issued()
+ {
+ $this->expectException(LogicException::class);
+ $this->expectExceptionMessage('The reCAPTCHA secret for [v3] doesn\'t exists.');
+
+ $instance = app(Captchavel::class);
+
+ $instance->useCredentials(3)->retrieve('token', '127.0.0.1');
+ }
+
+ public function test_exception_when_invalid_credentials_issued()
+ {
+ $this->expectException(LogicException::class);
+ $this->expectExceptionMessage('The reCAPTCHA v2 variant must be [checkbox], [invisible] or [android].');
+
+ $instance = app(Captchavel::class);
+
+ $instance->useCredentials(2, 'invalid')->retrieve('token', '127.0.0.1');
+ }
+
+ public function test_receives_v3_secret()
+ {
+ config(['captchavel.credentials.v3.secret' => 'secret']);
+
+ $mock = $this->mock(Factory::class);
+
+ $mock->shouldReceive('asForm')->withNoArgs()->once()->andReturnSelf();
+ $mock->shouldReceive('withOptions')->with(['version' => 2.0])->once()->andReturnSelf();
+ $mock->shouldReceive('post')
+ ->with(Captchavel::RECAPTCHA_ENDPOINT, [
+ 'secret' => 'secret',
+ 'response' => 'token',
+ 'remoteip' => '127.0.0.1',
+ ])
+ ->once()
+ ->andReturn(new Response(new GuzzleResponse(200, ['Content-type' => 'application/json'], json_encode([
+ 'success' => true,
+ 'score' => 0.5,
+ 'foo' => 'bar',
+ ]))));
+
+ $instance = app(Captchavel::class);
+
+ $this->assertNull($instance->getResponse());
+
+ $score = $instance->useCredentials(3)->retrieve('token', '127.0.0.1');
+
+ $this->assertSame($score, $instance->getResponse());
+ $this->assertInstanceOf(ReCaptchaResponse::class, $score);
+ $this->assertTrue($score->success);
+ $this->assertSame(0.5, $score->score);
+ $this->assertSame('bar', $score->foo);
+ }
+}
diff --git a/tests/ExtendsRequestMethodTest.php b/tests/ExtendsRequestMethodTest.php
deleted file mode 100644
index cdd2268..0000000
--- a/tests/ExtendsRequestMethodTest.php
+++ /dev/null
@@ -1,60 +0,0 @@
- 'DarkGhostHunter\Captchavel\Facades\ReCaptcha'
- ];
- }
-
- protected function getPackageProviders($app)
- {
- return ['DarkGhostHunter\Captchavel\CaptchavelServiceProvider'];
- }
-
- public function testExtendsRequestMethod()
- {
- $requester = \Mockery::mock(RequestMethod::class);
- $requester->shouldReceive('submit')
- ->with(RequestParameters::class)
- ->andReturn(json_encode([
- 'success' => $success = true,
- 'score' => $score = 0.8,
- 'action' => $action = 'test-action',
- 'challenge_ts' => $challenge_ts = Carbon::now()->toIso8601ZuluString(),
- ]));
-
- config()->set('captchavel.request_method', $requester);
- config()->set('captchavel.secret', Str::random());
-
- $this->app->when(ReCaptcha::class)
- ->needs(RequestMethod::class)
- ->give(function () use ($requester) {
- return $requester;
- });
-
- $recaptcha = $this->app->make(ReCaptcha::class);
-
- $response = $recaptcha->verify('anytoken');
-
- $this->assertInstanceOf(Response::class, $response);
- $this->assertTrue($response->isSuccess());
- $this->assertEquals($response->getScore(), $score);
- $this->assertEquals($response->getAction(), $action);
- $this->assertEquals($response->getChallengeTs(), $challenge_ts);
- }
-
-}
diff --git a/tests/HelperTest.php b/tests/HelperTest.php
new file mode 100644
index 0000000..437eec1
--- /dev/null
+++ b/tests/HelperTest.php
@@ -0,0 +1,48 @@
+expectException(LogicException::class);
+ $this->expectExceptionMessage('The reCAPTCHA site key for [3] doesn\'t exists.');
+
+ captchavel(3);
+ }
+
+ public function test_retrieves_test_keys_by_default()
+ {
+ $this->assertSame(Captchavel::TEST_V2_KEY, captchavel('checkbox'));
+ $this->assertSame(Captchavel::TEST_V2_KEY, captchavel('invisible'));
+ $this->assertSame(Captchavel::TEST_V2_KEY, captchavel('android'));
+ }
+
+ public function test_retrieves_secrets()
+ {
+ config(['captchavel.credentials.v2' => [
+ 'checkbox' => ['key' => 'key-checkbox'],
+ 'invisible' => ['key' => 'key-invisible'],
+ 'android' => ['key' => 'key-android'],
+ ]]);
+
+ config(['captchavel.credentials.v3' => [
+ 'key' => 'key-score'
+ ]]);
+
+ $this->assertSame('key-checkbox', captchavel('checkbox'));
+ $this->assertSame('key-invisible', captchavel('invisible'));
+ $this->assertSame('key-android', captchavel('android'));
+ $this->assertSame('key-score', captchavel('score'));
+ $this->assertSame('key-score', captchavel('v3'));
+ $this->assertSame('key-score', captchavel('3'));
+ $this->assertSame('key-score', captchavel(3));
+ }
+}
diff --git a/tests/Http/Middleware/ChallengeMiddlewareTest.php b/tests/Http/Middleware/ChallengeMiddlewareTest.php
new file mode 100644
index 0000000..4b6d62b
--- /dev/null
+++ b/tests/Http/Middleware/ChallengeMiddlewareTest.php
@@ -0,0 +1,349 @@
+afterApplicationCreated(function () {
+ $this->createsRoutes();
+ config(['captchavel.fake' => false]);
+ });
+
+ parent::setUp();
+ }
+
+ public function test_exception_if_no_challenge_specified()
+ {
+ Route::post('test', function () {
+ return 'ok';
+ })->middleware('recaptcha.v2');
+
+ $this->post('test')->assertStatus(500);
+
+ $this->postJson('test')->assertJson(['message' => 'Server Error']);
+ }
+
+ public function test_bypass_if_not_enabled()
+ {
+ config(['captchavel.enable' => false]);
+
+ $event = Event::fake();
+
+ $this->mock(Captchavel::class)->shouldNotReceive('useCredentials', 'retrieve');
+
+ $this->post('v2/checkbox')->assertOk();
+ $this->post('v2/invisible')->assertOk();
+ $this->post('v2/android')->assertOk();
+
+ $event->assertNotDispatched(ReCaptchaResponseReceived::class);
+ }
+
+ public function test_fakes_success()
+ {
+ config(['captchavel.fake' => true]);
+
+ $this->post('v2/checkbox')->assertOk();
+ $this->post('v2/checkbox/input_bar')->assertOk();
+ $this->post('v2/invisible')->assertOk();
+ $this->post('v2/invisible/input_bar')->assertOk();
+ $this->post('v2/android')->assertOk();
+ $this->post('v2/android/input_bar')->assertOk();
+ }
+
+ public function test_validates_if_real()
+ {
+ $event = Event::fake();
+
+ $mock = $this->mock(Captchavel::class);
+
+ $mock->shouldReceive('useCredentials')->once()->with(2, 'checkbox')->andReturnSelf();
+ $mock->shouldReceive('useCredentials')->once()->with(2, 'invisible')->andReturnSelf();
+ $mock->shouldReceive('useCredentials')->once()->with(2, 'android')->andReturnSelf();
+
+ $mock->shouldReceive('retrieve')
+ ->times(3)
+ ->with('token', '127.0.0.1')
+ ->andReturn(new ReCaptchaResponse([
+ 'success' => true,
+ 'score' => 0.5,
+ 'foo' => 'bar'
+ ]));
+
+ $this->post('v2/checkbox', [
+ Captchavel::INPUT => 'token'
+ ])->assertOk();
+ $this->post('v2/invisible', [
+ Captchavel::INPUT => 'token'
+ ])->assertOk();
+ $this->post('v2/android', [
+ Captchavel::INPUT => 'token'
+ ])->assertOk();
+
+ $event->assertDispatchedTimes(ReCaptchaResponseReceived::class, 3);
+ }
+
+ public function test_uses_custom_input()
+ {
+ $event = Event::fake();
+
+ $mock = $this->mock(Captchavel::class);
+
+ $mock->shouldReceive('useCredentials')->once()->with(2, 'checkbox')->andReturnSelf();
+ $mock->shouldReceive('useCredentials')->once()->with(2, 'invisible')->andReturnSelf();
+ $mock->shouldReceive('useCredentials')->once()->with(2, 'android')->andReturnSelf();
+
+ $mock->shouldReceive('retrieve')
+ ->times(3)
+ ->with('token', '127.0.0.1')
+ ->andReturn(new ReCaptchaResponse([
+ 'success' => true,
+ 'score' => 0.5,
+ 'foo' => 'bar'
+ ]));
+
+ $this->post('v2/checkbox/input_bar', [
+ 'bar' => 'token'
+ ])->assertOk();
+ $this->post('v2/invisible/input_bar', [
+ 'bar' => 'token'
+ ])->assertOk();
+ $this->post('v2/android/input_bar', [
+ 'bar' => 'token'
+ ])->assertOk();
+
+ $event->assertDispatchedTimes(ReCaptchaResponseReceived::class, 3);
+ }
+
+ public function test_exception_when_token_absent()
+ {
+ $event = Event::fake();
+
+ $mock = $this->mock(Captchavel::class);
+
+ $mock->shouldNotReceive('useCredentials', 'retrieve');
+
+ $this->post('v2/checkbox')->assertRedirect('/');
+ $this->postJson('v2/checkbox')->assertJsonValidationErrors(Captchavel::INPUT);
+ $this->post('v2/invisible')->assertRedirect('/');
+ $this->postJson('v2/invisible')->assertJsonValidationErrors(Captchavel::INPUT);
+ $this->post('v2/android')->assertRedirect('/');
+ $this->postJson('v2/android')->assertJsonValidationErrors(Captchavel::INPUT);
+
+ $this->post('v2/checkbox/input_bar')->assertRedirect('/');
+ $this->postJson('v2/checkbox/input_bar')->assertJsonValidationErrors('bar');
+ $this->post('v2/invisible/input_bar')->assertRedirect('/');
+ $this->postJson('v2/invisible/input_bar')->assertJsonValidationErrors('bar');
+ $this->post('v2/android/input_bar')->assertRedirect('/');
+ $this->postJson('v2/android/input_bar')->assertJsonValidationErrors('bar');
+
+ $event->assertNotDispatched(ReCaptchaResponseReceived::class);
+ }
+
+ public function test_exception_when_response_invalid()
+ {
+ $event = Event::fake();
+
+ $mock = $this->mock(Captchavel::class);
+
+ $mock->shouldReceive('useCredentials')->twice()->with(2, 'checkbox')->andReturnSelf();
+ $mock->shouldReceive('useCredentials')->twice()->with(2, 'invisible')->andReturnSelf();
+ $mock->shouldReceive('useCredentials')->twice()->with(2, 'android')->andReturnSelf();
+
+ $mock->shouldReceive('retrieve')
+ ->times(6)
+ ->with('token', '127.0.0.1')
+ ->andReturn(new ReCaptchaResponse([
+ 'success' => false,
+ ]));
+
+ $this->post('v2/checkbox', [Captchavel::INPUT => 'token'])->assertRedirect('/');
+ $this->postJson('v2/checkbox', [Captchavel::INPUT => 'token'])->assertJsonValidationErrors(Captchavel::INPUT);
+ $this->post('v2/invisible', [Captchavel::INPUT => 'token'])->assertRedirect('/');
+ $this->postJson('v2/invisible', [Captchavel::INPUT => 'token'])->assertJsonValidationErrors(Captchavel::INPUT);
+ $this->post('v2/android', [Captchavel::INPUT => 'token'])->assertRedirect('/');
+ $this->postJson('v2/android', [Captchavel::INPUT => 'token'])->assertJsonValidationErrors(Captchavel::INPUT);
+
+ $event->assertDispatchedTimes(ReCaptchaResponseReceived::class, 6);
+ }
+
+ public function test_no_error_if_not_hostname_issued()
+ {
+ config(['captchavel.hostname' => null]);
+
+ $event = Event::fake();
+
+ $mock = $this->mock(Captchavel::class);
+
+ $mock->shouldReceive('useCredentials')->once()->with(2, 'checkbox')->andReturnSelf();
+ $mock->shouldReceive('useCredentials')->once()->with(2, 'invisible')->andReturnSelf();
+ $mock->shouldReceive('useCredentials')->once()->with(2, 'android')->andReturnSelf();
+
+ $mock->shouldReceive('retrieve')
+ ->times(3)
+ ->with('token', '127.0.0.1')
+ ->andReturn(new ReCaptchaResponse([
+ 'success' => true,
+ 'hostname' => 'foo'
+ ]));
+
+ $this->post('v2/checkbox', [Captchavel::INPUT => 'token'])->assertOk();
+ $this->post('v2/invisible', [Captchavel::INPUT => 'token'])->assertOk();
+ $this->post('v2/android', [Captchavel::INPUT => 'token'])->assertOk();
+
+ $event->assertDispatchedTimes(ReCaptchaResponseReceived::class, 3);
+ }
+
+ public function test_no_error_if_not_hostname_same()
+ {
+ config(['captchavel.hostname' => 'foo']);
+
+ $event = Event::fake();
+
+ $mock = $this->mock(Captchavel::class);
+
+ $mock->shouldReceive('useCredentials')->once()->with(2, 'checkbox')->andReturnSelf();
+ $mock->shouldReceive('useCredentials')->once()->with(2, 'invisible')->andReturnSelf();
+ $mock->shouldReceive('useCredentials')->once()->with(2, 'android')->andReturnSelf();
+
+ $mock->shouldReceive('retrieve')
+ ->times(3)
+ ->with('token', '127.0.0.1')
+ ->andReturn(new ReCaptchaResponse([
+ 'success' => true,
+ 'hostname' => 'foo'
+ ]));
+
+ $this->post('v2/checkbox', [Captchavel::INPUT => 'token'])->assertOk();
+ $this->post('v2/invisible', [Captchavel::INPUT => 'token'])->assertOk();
+ $this->post('v2/android', [Captchavel::INPUT => 'token'])->assertOk();
+
+ $event->assertDispatchedTimes(ReCaptchaResponseReceived::class, 3);
+ }
+
+ public function test_exception_if_hostname_not_equal()
+ {
+ config(['captchavel.hostname' => 'bar']);
+
+ $event = Event::fake();
+
+ $mock = $this->mock(Captchavel::class);
+
+ $mock->shouldReceive('useCredentials')->twice()->with(2, 'checkbox')->andReturnSelf();
+ $mock->shouldReceive('useCredentials')->twice()->with(2, 'invisible')->andReturnSelf();
+ $mock->shouldReceive('useCredentials')->twice()->with(2, 'android')->andReturnSelf();
+
+ $mock->shouldReceive('retrieve')
+ ->times(6)
+ ->with('token', '127.0.0.1')
+ ->andReturn(new ReCaptchaResponse([
+ 'success' => true,
+ 'hostname' => 'foo'
+ ]));
+
+ $this->post('v2/checkbox', [Captchavel::INPUT => 'token'])->assertRedirect('/');
+ $this->postJson('v2/checkbox', [Captchavel::INPUT => 'token'])->assertJsonValidationErrors('hostname');
+ $this->post('v2/invisible', [Captchavel::INPUT => 'token'])->assertRedirect('/');
+ $this->postJson('v2/invisible', [Captchavel::INPUT => 'token'])->assertJsonValidationErrors('hostname');
+ $this->post('v2/android', [Captchavel::INPUT => 'token'])->assertRedirect('/');
+ $this->postJson('v2/android', [Captchavel::INPUT => 'token'])->assertJsonValidationErrors('hostname');
+
+ $event->assertDispatchedTimes(ReCaptchaResponseReceived::class, 6);
+ }
+
+ public function test_no_error_if_no_apk_issued()
+ {
+ config(['captchavel.apk_package_name' => null]);
+
+ $event = Event::fake();
+
+ $mock = $this->mock(Captchavel::class);
+
+ $mock->shouldReceive('useCredentials')->once()->with(2, 'checkbox')->andReturnSelf();
+ $mock->shouldReceive('useCredentials')->once()->with(2, 'invisible')->andReturnSelf();
+ $mock->shouldReceive('useCredentials')->once()->with(2, 'android')->andReturnSelf();
+
+ $mock->shouldReceive('retrieve')
+ ->times(3)
+ ->with('token', '127.0.0.1')
+ ->andReturn(new ReCaptchaResponse([
+ 'success' => true,
+ 'apk_package_name' => 'foo'
+ ]));
+
+ $this->post('v2/checkbox', [Captchavel::INPUT => 'token'])->assertOk();
+ $this->post('v2/invisible', [Captchavel::INPUT => 'token'])->assertOk();
+ $this->post('v2/android', [Captchavel::INPUT => 'token'])->assertOk();
+
+ $event->assertDispatchedTimes(ReCaptchaResponseReceived::class, 3);
+ }
+
+ public function test_no_error_if_no_apk_same()
+ {
+ config(['captchavel.apk_package_name' => 'foo']);
+
+ $event = Event::fake();
+
+ $mock = $this->mock(Captchavel::class);
+
+ $mock->shouldReceive('useCredentials')->once()->with(2, 'checkbox')->andReturnSelf();
+ $mock->shouldReceive('useCredentials')->once()->with(2, 'invisible')->andReturnSelf();
+ $mock->shouldReceive('useCredentials')->once()->with(2, 'android')->andReturnSelf();
+
+ $mock->shouldReceive('retrieve')
+ ->times(3)
+ ->with('token', '127.0.0.1')
+ ->andReturn(new ReCaptchaResponse([
+ 'success' => true,
+ 'apk_package_name' => 'foo'
+ ]));
+
+ $this->post('v2/checkbox', [Captchavel::INPUT => 'token'])->assertOk();
+ $this->post('v2/invisible', [Captchavel::INPUT => 'token'])->assertOk();
+ $this->post('v2/android', [Captchavel::INPUT => 'token'])->assertOk();
+
+ $event->assertDispatchedTimes(ReCaptchaResponseReceived::class, 3);
+ }
+
+ public function test_exception_if_apk_not_equal()
+ {
+ config(['captchavel.apk_package_name' => 'bar']);
+
+ $event = Event::fake();
+
+ $mock = $this->mock(Captchavel::class);
+
+ $mock->shouldReceive('useCredentials')->twice()->with(2, 'checkbox')->andReturnSelf();
+ $mock->shouldReceive('useCredentials')->twice()->with(2, 'invisible')->andReturnSelf();
+ $mock->shouldReceive('useCredentials')->twice()->with(2, 'android')->andReturnSelf();
+
+ $mock->shouldReceive('retrieve')
+ ->times(6)
+ ->with('token', '127.0.0.1')
+ ->andReturn(new ReCaptchaResponse([
+ 'success' => true,
+ 'apk_package_name' => 'foo'
+ ]));
+
+ $this->post('v2/checkbox', [Captchavel::INPUT => 'token'])->assertRedirect('/');
+ $this->postJson('v2/checkbox', [Captchavel::INPUT => 'token'])->assertJsonValidationErrors('apk_package_name');
+ $this->post('v2/invisible', [Captchavel::INPUT => 'token'])->assertRedirect('/');
+ $this->postJson('v2/invisible', [Captchavel::INPUT => 'token'])->assertJsonValidationErrors('apk_package_name');
+ $this->post('v2/android', [Captchavel::INPUT => 'token'])->assertRedirect('/');
+ $this->postJson('v2/android', [Captchavel::INPUT => 'token'])->assertJsonValidationErrors('apk_package_name');
+
+ $event->assertDispatchedTimes(ReCaptchaResponseReceived::class, 6);
+ }
+}
diff --git a/tests/Http/Middleware/ScoreMiddlewareTest.php b/tests/Http/Middleware/ScoreMiddlewareTest.php
new file mode 100644
index 0000000..738fe2b
--- /dev/null
+++ b/tests/Http/Middleware/ScoreMiddlewareTest.php
@@ -0,0 +1,487 @@
+afterApplicationCreated(function () {
+ $this->createsRoutes();
+ config(['captchavel.fake' => false]);
+ });
+
+ parent::setUp();
+ }
+
+ public function test_bypass_if_not_enabled()
+ {
+ config(['captchavel.enable' => false]);
+
+ $event = Event::fake();
+
+ $this->mock(Captchavel::class)->shouldNotReceive('useCredentials', 'retrieve');
+
+ $this->post('v3/default')->assertOk();
+
+ $event->assertNotDispatched(ReCaptchaResponseReceived::class);
+ }
+
+ public function test_validates_if_real()
+ {
+ $mock = $this->mock(Captchavel::class);
+
+ $event = Event::fake();
+
+ $mock->shouldReceive('useCredentials')
+ ->with(3, null)
+ ->andReturnSelf();
+ $mock->shouldReceive('retrieve')
+ ->with('token', '127.0.0.1')
+ ->andReturn(new ReCaptchaResponse([
+ 'success' => true,
+ 'score' => 0.5,
+ 'foo' => 'bar'
+ ]));
+
+ $this->post('v3/default', [
+ Captchavel::INPUT => 'token'
+ ])
+ ->assertOk()
+ ->assertExactJson([
+ 'success' => true,
+ 'score' => 0.5,
+ 'foo' => 'bar'
+ ]);
+
+ $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
+ return $event->response->foo === 'bar' && $event->request instanceof Request;
+ });
+ }
+
+ public function test_fakes_human_response_automatically()
+ {
+ config(['captchavel.fake' => true]);
+
+ $event = Event::fake();
+
+ $this->post('v3/default')
+ ->assertOk()
+ ->assertExactJson([
+ 'success' => true,
+ 'score' => 1,
+ 'action' => null,
+ 'hostname' => null,
+ 'apk_package_name' => null,
+ ]);
+
+ $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
+ return $event->response->success === true && $event->request instanceof Request;
+ });
+ }
+
+ public function test_fakes_robot_response_if_input_is_robot_present()
+ {
+ config(['captchavel.fake' => true]);
+
+ $event = Event::fake();
+
+ $this->post('v3/default', ['is_robot' => 'on'])
+ ->assertOk()
+ ->assertExactJson([
+ 'success' => true,
+ 'score' => 0,
+ 'action' => null,
+ 'hostname' => null,
+ 'apk_package_name' => null,
+ ]);
+
+ $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
+ return $event->response->success === true && $event->request instanceof Request;
+ });
+ }
+
+ public function test_uses_custom_threshold()
+ {
+ $mock = $this->mock(Captchavel::class);
+
+ $mock->shouldReceive('useCredentials')->andReturnSelf();
+ $mock->shouldReceive('retrieve')->andReturn(new ReCaptchaResponse([
+ 'success' => true,
+ 'score' => 0.7,
+ 'foo' => 'bar'
+ ]));
+
+ $event = Event::fake();
+
+ Route::post('test', function (ReCaptchaResponse $response) {
+ return [$response->isHuman(), $response->isRobot(), $response->score];
+ })->middleware('recaptcha.v3:0.7');
+
+ $this->post('test', [
+ Captchavel::INPUT => 'token'
+ ])
+ ->assertOk()
+ ->assertExactJson([
+ true, false, 0.7
+ ]);
+
+ $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
+ return $event->response->success === true && $event->request instanceof Request;
+ });
+ }
+
+ public function test_uses_custom_input()
+ {
+ $mock = $this->mock(Captchavel::class);
+
+ $mock->shouldReceive('useCredentials')
+ ->andReturnSelf();
+ $mock->shouldReceive('retrieve')
+ ->with('token', '127.0.0.1')
+ ->andReturn(new ReCaptchaResponse([
+ 'success' => true,
+ 'score' => 0.7,
+ 'foo' => 'bar'
+ ]));
+
+ $event = Event::fake();
+
+ Route::post('test', function (ReCaptchaResponse $response) {
+ return $response;
+ })->middleware('recaptcha.v3:null,null,foo');
+
+ $this->post('test', [
+ 'foo' => 'token'
+ ])
+ ->assertOk()
+ ->assertExactJson([
+ 'success' => true,
+ 'score' => 0.7,
+ 'foo' => 'bar',
+ ]);
+
+ $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
+ return $event->response->success === true && $event->request instanceof Request;
+ });
+ }
+
+ public function test_exception_when_token_absent()
+ {
+ $event = Event::fake();
+
+ $this->post('v3/default', [
+ 'foo' => 'bar'
+ ])->assertRedirect('/');
+
+ $this->postJson('v3/default', [
+ 'foo' => 'bar'
+ ])->assertJsonValidationErrors(Captchavel::INPUT);
+
+ $event->assertNotDispatched(ReCaptchaResponseReceived::class);
+ }
+
+ public function test_exception_when_response_invalid()
+ {
+ $event = Event::fake();
+
+ $mock = $this->mock(Captchavel::class);
+
+ $mock->shouldReceive('useCredentials')
+ ->andReturnSelf();
+ $mock->shouldReceive('retrieve')
+ ->with('token', '127.0.0.1')
+ ->andReturn(new ReCaptchaResponse([
+ 'success' => false,
+ ]));
+
+ $this->post('v3/default', [
+ Captchavel::INPUT => 'token'
+ ])->assertRedirect('/');
+
+ $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
+ return $event->response->success === false && $event->request instanceof Request;
+ });
+
+ $this->postJson('v3/default', [
+ 'foo' => 'bar'
+ ])->assertJsonValidationErrors(Captchavel::INPUT);
+ }
+
+ public function test_no_error_if_not_hostname_issued()
+ {
+ config(['captchavel.hostname' => null]);
+
+ $mock = $this->mock(Captchavel::class);
+
+ $mock->shouldReceive('useCredentials')
+ ->andReturnSelf();
+ $mock->shouldReceive('retrieve')
+ ->with('token', '127.0.0.1')
+ ->andReturn(new ReCaptchaResponse([
+ 'success' => true,
+ 'hostname' => 'foo',
+ ]));
+
+ $event = Event::fake();
+
+ $this->post('v3/default', [
+ Captchavel::INPUT => 'token'
+ ])->assertOk();
+
+ $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
+ return $event->response->success === true && $event->request instanceof Request;
+ });
+
+ $this->postJson('v3/default', [
+ Captchavel::INPUT => 'token'
+ ])->assertOk();
+ }
+
+ public function test_no_error_if_hostname_same()
+ {
+ config(['captchavel.hostname' => 'bar']);
+
+ $mock = $this->mock(Captchavel::class);
+
+ $mock->shouldReceive('useCredentials')
+ ->andReturnSelf();
+ $mock->shouldReceive('retrieve')
+ ->with('token', '127.0.0.1')
+ ->andReturn(new ReCaptchaResponse([
+ 'success' => true,
+ 'hostname' => 'bar',
+ ]));
+
+ $event = Event::fake();
+
+ $this->post('v3/default', [
+ Captchavel::INPUT => 'token'
+ ])->assertOk();
+
+ $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
+ return $event->response->success === true && $event->request instanceof Request;
+ });
+
+ $this->postJson('v3/default', [
+ Captchavel::INPUT => 'token'
+ ])->assertOk();
+ }
+
+ public function test_exception_if_hostname_not_equal()
+ {
+ config(['captchavel.hostname' => 'bar']);
+
+ $mock = $this->mock(Captchavel::class);
+
+ $mock->shouldReceive('useCredentials')
+ ->andReturnSelf();
+ $mock->shouldReceive('retrieve')
+ ->with('token', '127.0.0.1')
+ ->andReturn(new ReCaptchaResponse([
+ 'success' => true,
+ 'hostname' => 'foo',
+ ]));
+
+ $event = Event::fake();
+
+ $this->post('v3/default', [
+ Captchavel::INPUT => 'token'
+ ])->assertRedirect('/');
+
+ $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
+ return $event->response->hostname === 'foo' && $event->request instanceof Request;
+ });
+
+ $this->postJson('v3/default', [
+ Captchavel::INPUT => 'token'
+ ])->assertJsonValidationErrors('hostname');
+ }
+
+ public function test_no_error_if_no_apk_issued()
+ {
+ config(['captchavel.apk_package_name' => null]);
+
+ $mock = $this->mock(Captchavel::class);
+
+ $mock->shouldReceive('useCredentials')
+ ->andReturnSelf();
+ $mock->shouldReceive('retrieve')
+ ->with('token', '127.0.0.1')
+ ->andReturn(new ReCaptchaResponse([
+ 'success' => true,
+ 'apk_package_name' => 'foo',
+ ]));
+
+ $event = Event::fake();
+
+ $this->post('v3/default', [
+ Captchavel::INPUT => 'token'
+ ])->assertOk();
+
+ $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
+ return $event->response->apk_package_name === 'foo' && $event->request instanceof Request;
+ });
+
+ $this->postJson('v3/default', [
+ Captchavel::INPUT => 'token'
+ ])->assertOk();
+ }
+
+ public function test_no_error_if_apk_same()
+ {
+ config(['captchavel.apk_package_name' => 'foo']);
+
+ $mock = $this->mock(Captchavel::class);
+
+ $mock->shouldReceive('useCredentials')
+ ->andReturnSelf();
+ $mock->shouldReceive('retrieve')
+ ->with('token', '127.0.0.1')
+ ->andReturn(new ReCaptchaResponse([
+ 'success' => true,
+ 'apk_package_name' => 'foo',
+ ]));
+
+ $event = Event::fake();
+
+ $this->post('v3/default', [
+ Captchavel::INPUT => 'token'
+ ])->assertOk();
+
+ $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
+ return $event->response->apk_package_name === 'foo' && $event->request instanceof Request;
+ });
+
+ $this->postJson('v3/default', [
+ Captchavel::INPUT => 'token'
+ ])->assertOk();
+ }
+
+ public function test_exception_if_apk_not_equal()
+ {
+ config(['captchavel.apk_package_name' => 'bar']);
+
+ $mock = $this->mock(Captchavel::class);
+
+ $mock->shouldReceive('useCredentials')
+ ->andReturnSelf();
+ $mock->shouldReceive('retrieve')
+ ->with('token', '127.0.0.1')
+ ->andReturn(new ReCaptchaResponse([
+ 'success' => true,
+ 'apk_package_name' => null,
+ ]));
+
+ $event = Event::fake();
+
+ $this->post('v3/default', [
+ Captchavel::INPUT => 'token'
+ ])->assertRedirect('/');
+
+ $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
+ return $event->response->apk_package_name === null && $event->request instanceof Request;
+ });
+
+ $this->postJson('v3/default', [
+ Captchavel::INPUT => 'token'
+ ])->assertJsonValidationErrors('apk_package_name');
+ }
+
+ public function test_no_error_if_no_action()
+ {
+ $mock = $this->mock(Captchavel::class);
+
+ $mock->shouldReceive('useCredentials')
+ ->andReturnSelf();
+ $mock->shouldReceive('retrieve')
+ ->with('token', '127.0.0.1')
+ ->andReturn(new ReCaptchaResponse([
+ 'success' => true,
+ 'apk_package_name' => null,
+ 'action' => 'foo',
+ ]));
+
+ $event = Event::fake();
+
+ Route::post('test', function (ReCaptchaResponse $response) {
+ return $response;
+ })->middleware('recaptcha.v3:null,null');
+
+ $this->post('test', [
+ Captchavel::INPUT => 'token'
+ ])->assertOk();
+
+ $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
+ return $event->response->action === 'foo' && $event->request instanceof Request;
+ });
+ }
+
+ public function test_no_error_if_action_same()
+ {
+ $mock = $this->mock(Captchavel::class);
+
+ $mock->shouldReceive('useCredentials')
+ ->andReturnSelf();
+ $mock->shouldReceive('retrieve')
+ ->with('token', '127.0.0.1')
+ ->andReturn(new ReCaptchaResponse([
+ 'success' => true,
+ 'apk_package_name' => null,
+ 'action' => 'foo',
+ ]));
+
+ $event = Event::fake();
+
+ Route::post('test', function (ReCaptchaResponse $response) {
+ return $response;
+ })->middleware('recaptcha.v3:null,foo');
+
+ $this->post('test', [
+ Captchavel::INPUT => 'token'
+ ])->assertOk();
+
+ $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
+ return $event->response->action === 'foo' && $event->request instanceof Request;
+ });
+ }
+
+ public function test_exception_if_action_not_equal()
+ {
+ $mock = $this->mock(Captchavel::class);
+
+ $mock->shouldReceive('useCredentials')
+ ->andReturnSelf();
+ $mock->shouldReceive('retrieve')
+ ->with('token', '127.0.0.1')
+ ->andReturn(new ReCaptchaResponse([
+ 'success' => true,
+ 'apk_package_name' => null,
+ 'action' => 'foo',
+ ]));
+
+ Route::post('test', function (ReCaptchaResponse $response) {
+ return $response;
+ })->middleware('recaptcha.v3:null,bar');
+
+ $this->post('test', [
+ Captchavel::INPUT => 'token'
+ ])->assertRedirect('/');
+
+ $this->postJson('test', [
+ Captchavel::INPUT => 'token'
+ ])->assertJsonValidationErrors('action');
+ }
+}
diff --git a/tests/Http/Middleware/UsesRoutesWithMiddleware.php b/tests/Http/Middleware/UsesRoutesWithMiddleware.php
new file mode 100644
index 0000000..befffe3
--- /dev/null
+++ b/tests/Http/Middleware/UsesRoutesWithMiddleware.php
@@ -0,0 +1,58 @@
+ true]);
+
+ Route::post('v3/default', function (ReCaptchaResponse $response) {
+ return $response;
+ })->middleware('recaptcha.v3');
+
+ Route::post('v3/threshold_1', function (ReCaptchaResponse $response) {
+ return $response;
+ })->middleware('recaptcha.v3:1.0');
+
+ Route::post('v3/threshold_0', function (ReCaptchaResponse $response) {
+ return $response;
+ })->middleware('recaptcha.v3:0');
+
+ Route::post('v3/action_foo', function (ReCaptchaResponse $response) {
+ return $response;
+ })->middleware('recaptcha.v3:null,foo');
+
+ Route::post('v3/input_bar', function (ReCaptchaResponse $response) {
+ return $response;
+ })->middleware('recaptcha.v3:null,null,bar');
+
+ Route::post('v2/checkbox', function (ReCaptchaResponse $response) {
+ return $response;
+ })->middleware('recaptcha.v2:checkbox');
+
+ Route::post('v2/checkbox/input_bar', function (ReCaptchaResponse $response) {
+ return $response;
+ })->middleware('recaptcha.v2:checkbox,bar');
+
+ Route::post('v2/invisible', function (ReCaptchaResponse $response) {
+ return $response;
+ })->middleware('recaptcha.v2:invisible');
+
+ Route::post('v2/invisible/input_bar', function (ReCaptchaResponse $response) {
+ return $response;
+ })->middleware('recaptcha.v2:invisible,bar');
+
+ Route::post('v2/android', function (ReCaptchaResponse $response) {
+ return $response;
+ })->middleware('recaptcha.v2:android');
+
+ Route::post('v2/android/input_bar', function (ReCaptchaResponse $response) {
+ return $response;
+ })->middleware('recaptcha.v2:android,bar');
+ }
+}
diff --git a/tests/Http/ReCaptchaResponseTest.php b/tests/Http/ReCaptchaResponseTest.php
new file mode 100644
index 0000000..5b99c23
--- /dev/null
+++ b/tests/Http/ReCaptchaResponseTest.php
@@ -0,0 +1,23 @@
+expectException(LogicException::class);
+ $this->expectExceptionMessage('This is not a reCAPTCHA v3 response, or the score is absent.');
+
+ (new ReCaptchaResponse([
+ 'success' => true,
+ ]))->isHuman();
+ }
+}
diff --git a/tests/Middleware/CheckRecaptchaTest.php b/tests/Middleware/CheckRecaptchaTest.php
deleted file mode 100644
index 1766cfd..0000000
--- a/tests/Middleware/CheckRecaptchaTest.php
+++ /dev/null
@@ -1,167 +0,0 @@
- 'DarkGhostHunter\Captchavel\Facades\ReCaptcha'
- ];
- }
-
- protected function getPackageProviders($app)
- {
- return ['DarkGhostHunter\Captchavel\CaptchavelServiceProvider'];
- }
-
- /**
- * Define environment setup.
- *
- * @param \Illuminate\Foundation\Application $app
- * @return void
- */
- protected function getEnvironmentSetUp($app)
- {
- $app['env'] = 'local';
-
- $app->make('config')->set('captchavel.enable_local', true);
- $app->make('config')->set('captchavel.secret', 'test-secret');
- $app->make('config')->set('captchavel.key', 'test-key');
-
- $app->make('router')->get('test-get-with-middleware', function () { return 'true'; })->middleware('recaptcha');
- $app->make('router')->get('test-get', function () { return 'true'; });
- $app->make('router')->post('test-post', function () { return 'true'; })->middleware('recaptcha');
- }
-
- public function testSameRecaptchaInstance()
- {
- $mockRequester = \Mockery::mock(RequestMethod::class);
- $mockRequester->shouldReceive('submit')->andReturn(json_encode([
- 'success' => true,
- 'score' => 0.8,
- 'action' => '/testpost',
- 'challenge_ts' => Carbon::now()->toIso8601ZuluString(),
- ]));
-
- $this->app->when(ReCaptchaFactory::class)
- ->needs(RequestMethod::class)
- ->give(function () use ($mockRequester) {
- return $mockRequester;
- });
-
- $this->post('test-post', [
- '_recaptcha' => Str::random(356)
- ])->assertOk();
-
- $this->assertEquals(app(ReCaptcha::class), app('recaptcha'));
- $this->assertEquals(app(ReCaptcha::class), recaptcha());
- $this->assertNotEquals(app(ReCaptcha::class), new ReCaptcha());
- $this->assertNotEquals(app('recaptcha'), new ReCaptcha());
- $this->assertNotEquals(recaptcha(), new ReCaptcha());
- }
-
- public function testRequestWithCaptchaValidates()
- {
- $mockRequester = \Mockery::mock(RequestMethod::class);
- $mockRequester->shouldReceive('submit')->andReturn(json_encode([
- 'success' => true,
- 'score' => 0.8,
- 'action' => '/testpost',
- 'challenge_ts' => Carbon::now()->toIso8601ZuluString(),
- ]));
-
- $this->app->when(ReCaptchaFactory::class)
- ->needs(RequestMethod::class)
- ->give(function ($app) use ($mockRequester) {
- return $mockRequester;
- });
-
- $this->post('test-post', [
- '_recaptcha' => Str::random(356)
- ])->assertOk();
- }
-
- public function testFailsOnNonPostMethod()
- {
- $this->app->make('router')->get('get-route', function () { return 'true'; })->middleware('recaptcha');
- $this->app->make('router')->match(['head'], 'head-route', function () { return 'true'; })->middleware('recaptcha');
-
- $response = $this->get('get-route');
-
- $response->assertStatus(200);
-
- $response = $this->call('head', 'head-route');
-
- $response->assertStatus(200);
- }
-
- public function testFailsInvalidToken()
- {
- $response = $this->post('test-post', [ '_recaptcha' => ['not.string']]);
-
- $response->assertStatus(500);
- $this->assertInstanceOf(InvalidRecaptchaException::class, $response->exception);
- }
-
- public function testFailsInvalidRecaptcha()
- {
- $mockRequester = \Mockery::mock(RequestMethod::class);
- $mockRequester->shouldReceive('submit')->andReturn(json_encode([
- 'success' => false,
- 'score' => 0.8,
- 'action' => '/testpost',
- 'challenge_ts' => Carbon::now()->toIso8601ZuluString(),
- ]));
-
- $this->app->when(ReCaptchaFactory::class)
- ->needs(RequestMethod::class)
- ->give(function ($app) use ($mockRequester) {
- return $mockRequester;
- });
-
- $response = $this->post('test-post', [ '_recaptcha' => Str::random(356)]);
-
- $response->assertStatus(500);
- $this->assertInstanceOf(FailedRecaptchaException::class, $response->exception);
- }
-
- public function testMiddlewareAcceptsParameter()
- {
- $mockReCaptchaFactory = \Mockery::mock(ReCaptchaFactory::class);
- $mockReCaptchaFactory->shouldReceive('setExpectedAction')
- ->once()
- ->andReturnSelf();
- $mockReCaptchaFactory->shouldReceive('verify')
- ->once()
- ->andReturn(new Response(true, [], null, null, null, 1.0, null));
-
- $this->app->when(CheckRecaptcha::class)
- ->needs(ReCaptchaFactory::class)
- ->give(function () use ($mockReCaptchaFactory) {
- return $mockReCaptchaFactory;
- });
-
- $this->app->make('router')
- ->post('test-post', function () {
- return 'reaches';
- })
- ->middleware('recaptcha:0.9');
-
- $this->post('test-post', [ '_recaptcha' => Str::random(356) ])
- ->assertOk();
- }
-}
diff --git a/tests/Middleware/InjectRecaptchaScriptTest.php b/tests/Middleware/InjectRecaptchaScriptTest.php
deleted file mode 100644
index 575530a..0000000
--- a/tests/Middleware/InjectRecaptchaScriptTest.php
+++ /dev/null
@@ -1,112 +0,0 @@
- 'DarkGhostHunter\Captchavel\Facades\ReCaptcha'
- ];
- }
-
- protected function getPackageProviders($app)
- {
- return ['DarkGhostHunter\Captchavel\CaptchavelServiceProvider'];
- }
-
- /**
- * Define environment setup.
- *
- * @param \Illuminate\Foundation\Application $app
- * @return void
- */
- protected function getEnvironmentSetUp($app)
- {
- $app['env'] = 'local';
-
- $app->make('config')->set('captchavel.enable_local', true);
- $app->make('config')->set('captchavel.secret', 'test-secret');
- $app->make('config')->set('captchavel.key', 'test-key');
-
- $this->afterApplicationCreated(function () {
-
- $router = $this->app->make('router');
-
- $router->get('test-get', function () {
- return response()->make(/** @lang HTML */ <<
-
-
-
-
-
- Document
-
-
-
-
-
-EOT
- );
- });
-
- $router->get('invalid-html', function () {
- return response()->make('Hellow');
- });
-
- $router->get('json', function () {
- return response()->json('');
- });
-
- });
- }
-
- public function testInjectsScriptAutomatically()
- {
- $response = $this->get('test-get')
- ->assertSee('Start Captchavel Script');
-
- $this->assertStringContainsString('api.js?render=test-key&onload=captchavelCallback', $response->getContent());
- }
-
- public function testDoesntInjectsOnInvalidHtml()
- {
- $response = $this->get('invalid-html')
- ->assertDontSee('Start Captchavel Script');
-
- $this->assertStringNotContainsString('api.js?render=test-key&onload=captchavelCallback', $response->getContent());
- }
-
- public function testDoesntInjectsOnJson()
- {
- $response = $this->get('json')
- ->assertDontSee('Start Captchavel Script');
-
- $this->assertStringNotContainsString('api.js?render=test-key&onload=captchavelCallback', $response->getContent());
- }
-
- public function testDoesntInjectsOnAjax()
- {
- $response = $this->get('test-get', [
- 'X-Requested-With' => 'XMLHttpRequest'
- ])
- ->assertDontSee('Start Captchavel Script');
-
- $this->assertStringNotContainsString('api.js?render=test-key&onload=captchavelCallback', $response->getContent());
- }
-
- public function testDoesntInjectsOnException()
- {
- $response = $this->get('route-doesnt-exists-will-trigger-exception')
- ->assertDontSee('Start Captchavel Script');
-
- $this->assertStringNotContainsString('api.js?render=test-key&onload=captchavelCallback', $response->getContent());
- }
-
-}
diff --git a/tests/Middleware/ThrottleRecaptchaTest.php b/tests/Middleware/ThrottleRecaptchaTest.php
deleted file mode 100644
index 6c34f88..0000000
--- a/tests/Middleware/ThrottleRecaptchaTest.php
+++ /dev/null
@@ -1,167 +0,0 @@
- 'DarkGhostHunter\Captchavel\Facades\ReCaptcha'
- ];
- }
-
- protected function getPackageProviders($app)
- {
- return ['DarkGhostHunter\Captchavel\CaptchavelServiceProvider'];
- }
-
- /**
- * Define environment setup.
- *
- * @param \Illuminate\Foundation\Application $app
- * @return void
- */
- protected function getEnvironmentSetUp($app)
- {
- $app['env'] = 'local';
-
- $app->make('config')->set('captchavel.enable_local', true);
- $app->make('config')->set('captchavel.secret', 'test-secret');
- $app->make('config')->set('captchavel.key', 'test-key');
-
- $app->make('router')->get('test-get-with-middleware', function () { return 'true'; })->middleware('recaptcha');
- $app->make('router')->get('test-get', function () { return 'true'; });
- $app->make('router')->post('test-post', function () { return 'true'; })->middleware('recaptcha');
- }
-
- public function testSameRecaptchaInstance()
- {
- $mockRequester = \Mockery::mock(RequestMethod::class);
- $mockRequester->shouldReceive('submit')->andReturn(json_encode([
- 'success' => true,
- 'score' => 0.8,
- 'action' => '/testpost',
- 'challenge_ts' => Carbon::now()->toIso8601ZuluString(),
- ]));
-
- $this->app->when(ReCaptchaFactory::class)
- ->needs(RequestMethod::class)
- ->give(function () use ($mockRequester) {
- return $mockRequester;
- });
-
- $this->post('test-post', [
- '_recaptcha' => Str::random(356)
- ])->assertOk();
-
- $this->assertEquals(app(ReCaptcha::class), app('recaptcha'));
- $this->assertEquals(app(ReCaptcha::class), recaptcha());
- $this->assertNotEquals(app(ReCaptcha::class), new ReCaptcha());
- $this->assertNotEquals(app('recaptcha'), new ReCaptcha());
- $this->assertNotEquals(recaptcha(), new ReCaptcha());
- }
-
- public function testRequestWithCaptchaValidates()
- {
- $mockRequester = \Mockery::mock(RequestMethod::class);
- $mockRequester->shouldReceive('submit')->andReturn(json_encode([
- 'success' => true,
- 'score' => 0.8,
- 'action' => '/testpost',
- 'challenge_ts' => Carbon::now()->toIso8601ZuluString(),
- ]));
-
- $this->app->when(ReCaptchaFactory::class)
- ->needs(RequestMethod::class)
- ->give(function ($app) use ($mockRequester) {
- return $mockRequester;
- });
-
- $this->post('test-post', [
- '_recaptcha' => Str::random(356)
- ])->assertOk();
- }
-
- public function testFailsOnNonPostMethod()
- {
- $this->app->make('router')->get('get-route', function () { return 'true'; })->middleware('recaptcha');
- $this->app->make('router')->match(['head'], 'head-route', function () { return 'true'; })->middleware('recaptcha');
-
- $response = $this->get('get-route');
-
- $response->assertStatus(200);
-
- $response = $this->call('head', 'head-route');
-
- $response->assertStatus(200);
- }
-
- public function testFailsInvalidToken()
- {
- $response = $this->post('test-post', [ '_recaptcha' => ['not.string']]);
-
- $response->assertStatus(500);
- $this->assertInstanceOf(InvalidRecaptchaException::class, $response->exception);
- }
-
- public function testFailsInvalidRecaptcha()
- {
- $mockRequester = \Mockery::mock(RequestMethod::class);
- $mockRequester->shouldReceive('submit')->andReturn(json_encode([
- 'success' => false,
- 'score' => 0.8,
- 'action' => '/testpost',
- 'challenge_ts' => Carbon::now()->toIso8601ZuluString(),
- ]));
-
- $this->app->when(ReCaptchaFactory::class)
- ->needs(RequestMethod::class)
- ->give(function ($app) use ($mockRequester) {
- return $mockRequester;
- });
-
- $response = $this->post('test-post', [ '_recaptcha' => Str::random(356)]);
-
- $response->assertStatus(500);
- $this->assertInstanceOf(FailedRecaptchaException::class, $response->exception);
- }
-
- public function testMiddlewareAcceptsParameter()
- {
- $mockReCaptchaFactory = \Mockery::mock(ReCaptchaFactory::class);
- $mockReCaptchaFactory->shouldReceive('setExpectedAction')
- ->once()
- ->andReturnSelf();
- $mockReCaptchaFactory->shouldReceive('verify')
- ->once()
- ->andReturn(new Response(true, [], null, null, null, 1.0, null));
-
- $this->app->when(CheckRecaptcha::class)
- ->needs(ReCaptchaFactory::class)
- ->give(function () use ($mockReCaptchaFactory) {
- return $mockReCaptchaFactory;
- });
-
- $this->app->make('router')
- ->post('test-post', function () {
- return 'reaches';
- })
- ->middleware('recaptcha:0.9');
-
- $this->post('test-post', [ '_recaptcha' => Str::random(356) ])
- ->assertOk();
- }
-}
diff --git a/tests/Middleware/TransparentRecaptchaTest.php b/tests/Middleware/TransparentRecaptchaTest.php
deleted file mode 100644
index 1756f8b..0000000
--- a/tests/Middleware/TransparentRecaptchaTest.php
+++ /dev/null
@@ -1,66 +0,0 @@
- 'DarkGhostHunter\Captchavel\Facades\ReCaptcha'
- ];
- }
-
- protected function getPackageProviders($app)
- {
- return ['DarkGhostHunter\Captchavel\CaptchavelServiceProvider'];
- }
-
- /**
- * Define environment setup.
- *
- * @param \Illuminate\Foundation\Application $app
- * @return void
- */
- protected function getEnvironmentSetUp($app)
- {
- $app->make('config')->set('captchavel.secret', 'test-secret');
- $app->make('config')->set('captchavel.key', 'test-key');
-
- $app->make('router')->post('test-post', function () {
- return 'true';
- })->middleware('recaptcha');
- }
-
- public function testTransparentMiddlewareReturnsHuman()
- {
- $this->post('test-post', [
- '_recaptcha' => null
- ])->assertSeeText('true');
-
- $this->assertTrue(recaptcha()->isHuman());
- }
-
- public function testTransparentMiddlewareReturnsRobotOnQuery()
- {
- $this->post('test-post?is_robot=null', [
- '_recaptcha' => null
- ])->assertSeeText('true');
-
- $this->assertFalse(recaptcha()->isRobot());
- }
-
- public function testTransparentMiddlewareReturnsRobotOnInput()
- {
- $this->post('test-post?is_robot=null', [
- '_recaptcha' => null,
- 'is_robot' => 'true'
- ])->assertSeeText('true');
-
- $this->assertFalse(recaptcha()->isRobot());
- }
-
-}
diff --git a/tests/RecaptchaResponseHolderTest.php b/tests/RecaptchaResponseHolderTest.php
deleted file mode 100644
index 3ee7c1c..0000000
--- a/tests/RecaptchaResponseHolderTest.php
+++ /dev/null
@@ -1,123 +0,0 @@
- 'DarkGhostHunter\Captchavel\Facades\ReCaptcha'
- ];
- }
-
- protected function getPackageProviders($app)
- {
- return ['DarkGhostHunter\Captchavel\CaptchavelServiceProvider'];
- }
-
- public function testReCaptchaResponse()
- {
- $response = \Mockery::mock(Response::class);
-
- $holder = new ReCaptcha();
-
- $holder->setResponse($response);
-
- $this->assertInstanceOf(Response::class, $holder->response());
- }
-
- public function testThreshold()
- {
- $holder = new ReCaptcha();
-
- $holder->setThreshold(0.4);
-
- $this->assertIsFloat($holder->getThreshold());
- }
-
- public function testSince()
- {
- $response = \Mockery::mock(Response::class);
- $response->shouldReceive('getChallengeTs')
- ->once()
- ->andReturn(Carbon::now()->toIso8601ZuluString());
-
- $holder = new ReCaptcha();
-
- $holder->setResponse($response);
-
- $this->assertInstanceOf(Carbon::class, $holder->since());
- }
-
- public function testThresholdOverScore()
- {
- $response = \Mockery::mock(Response::class);
- $response->shouldReceive('getScore')
- ->twice()
- ->andReturn(0.8);
-
- $holder = new ReCaptcha();
-
- $holder->setResponse($response);
- $holder->setThreshold(0.5);
-
- $this->assertTrue($holder->isHuman());
- $this->assertFalse($holder->isRobot());
- }
-
- public function testThresholdUnderScore()
- {
- $response = \Mockery::mock(Response::class);
- $response->shouldReceive('getScore')
- ->twice()
- ->andReturn(0.2);
-
- $holder = new ReCaptcha();
-
- $holder->setResponse($response);
- $holder->setThreshold(0.5);
-
- $this->assertTrue($holder->isRobot());
- $this->assertFalse($holder->isHuman());
- }
-
- public function testIsResolved()
- {
- $holder = new ReCaptcha();
-
- $this->assertFalse($holder->isResolved());
-
- $holder->setResponse(\Mockery::mock(Response::class));
-
- $this->assertTrue($holder->isResolved());
- }
-
- public function testExceptionOnHumanCheck()
- {
- $this->expectException(RecaptchaNotResolvedException::class);
-
- (new ReCaptcha())->isHuman();
- }
-
- public function testExceptionOnRobotCheck()
- {
- $this->expectException(RecaptchaNotResolvedException::class);
-
- (new ReCaptcha())->isRobot();
- }
-
- public function testExceptionOnSinceCheck()
- {
- $this->expectException(RecaptchaNotResolvedException::class);
-
- (new ReCaptcha())->since();
- }
-}
diff --git a/tests/RegistersPackage.php b/tests/RegistersPackage.php
new file mode 100644
index 0000000..c69a80c
--- /dev/null
+++ b/tests/RegistersPackage.php
@@ -0,0 +1,19 @@
+ 'DarkGhostHunter\Captchavel\Facades\Captchavel'
+ ];
+ }
+
+ protected function getPackageProviders($app)
+ {
+ return ['DarkGhostHunter\Captchavel\CaptchavelServiceProvider'];
+ }
+
+}
diff --git a/tests/ServiceProviderTest.php b/tests/ServiceProviderTest.php
deleted file mode 100644
index ce22dbd..0000000
--- a/tests/ServiceProviderTest.php
+++ /dev/null
@@ -1,178 +0,0 @@
- 'DarkGhostHunter\Captchavel\Facades\ReCaptcha'
- ];
- }
-
- protected function getPackageProviders($app)
- {
- return ['DarkGhostHunter\Captchavel\CaptchavelServiceProvider'];
- }
-
- public function testRegistersPackage()
- {
- $instance = $this->app->make('recaptcha');
-
- $this->assertInstanceOf(ReCaptcha::class, $instance);
- }
-
- public function testRecaptchaFacade()
- {
- $this->assertInstanceOf(ReCaptcha::class, \ReCaptcha::getFacadeRoot());
- }
-
- public function testReceivesConfig()
- {
- $this->assertEquals(
- include __DIR__.'/../config/captchavel.php',
- $this->app->make('config')->get('captchavel')
- );
- }
-
- public function testResolvesRecaptchaResponse()
- {
- config()->set('captchavel.secret', Str::random());
-
- $instance = app()->make(ReCaptchaFactory::class);
-
- $this->assertInstanceOf(ReCaptchaFactory::class, $instance);
- }
-
- public function testDoesntResolveRecaptchaWithoutSecret()
- {
- $this->expectException(\RuntimeException::class);
-
- $instance = app()->make(ReCaptchaFactory::class);
-
- $this->assertInstanceOf(ReCaptchaFactory::class, $instance);
- }
-
- public function testRegisterMiddleware()
- {
- $this->app['env'] = 'production';
-
- /** @var CaptchavelServiceProvider $provider */
- $provider = app()->make(CaptchavelServiceProvider::class, ['app' => $this->app]);
-
- $provider->boot();
-
- $middleware = $this->app->make('router')->getMiddleware();
-
- $this->assertArrayHasKey('recaptcha', $middleware);
- $this->assertEquals($middleware['recaptcha'], CheckRecaptcha::class);
- $this->assertEquals($middleware['recaptcha-inject'], InjectRecaptchaScript::class);
- }
-
- public function testDoesntRegisterMiddlewareOnTesting()
- {
- $this->assertFalse($this->app->make(Kernel::class)->hasMiddleware(CheckRecaptcha::class));
- $middleware = $this->app->make('router')->getMiddleware();
-
- $this->assertEquals(TransparentRecaptcha::class, $middleware['recaptcha']);
- }
-
- public function testRegisterTransparentMiddlewareOnNotProduction()
- {
- $this->app['env'] = 'local';
-
- /** @var CaptchavelServiceProvider $provider */
- $provider = $this->app->make(CaptchavelServiceProvider::class, ['app' => $this->app]);
-
- $provider->boot();
-
- $middleware = $this->app->make('router')->getMiddleware();
-
- $this->assertEquals(TransparentRecaptcha::class, $middleware['recaptcha']);
- }
-
- public function testRegisterInjectMiddlewareOnAuto()
- {
- $this->app['env'] = 'production';
-
- /** @var CaptchavelServiceProvider $provider */
- $provider = $this->app->make(CaptchavelServiceProvider::class, ['app' => $this->app]);
-
- $provider->boot();
-
- $this->assertTrue(
- $this->app->make(Kernel::class)->hasMiddleware(InjectRecaptchaScript::class)
- );
- }
-
- public function testDoesntRegisterInjectMiddlewareOnNonAuto()
- {
- $this->app['env'] = 'production';
- $this->app['config']->set('captchavel.mode', 'manual');
-
- /** @var CaptchavelServiceProvider $provider */
- $provider = $this->app->make(CaptchavelServiceProvider::class, ['app' => $this->app]);
-
- $provider->boot();
-
- $this->assertFalse(
- $this->app->make(Kernel::class)->hasMiddleware(InjectRecaptchaScript::class)
- );
- }
-
- public function testRegisterMiddlewareOnLocalTrue()
- {
- $this->app['env'] = 'local';
- $this->app['config']->set('captchavel.enable_local', true);
-
- /** @var CaptchavelServiceProvider $provider */
- $provider = $this->app->make(CaptchavelServiceProvider::class, ['app' => $this->app]);
-
- $provider->boot();
-
- /** @var \Illuminate\Routing\Router $router */
- $router = $this->app->make('router');
-
- $this->assertEquals(CheckRecaptcha::class, $router->getMiddleware()['recaptcha']);
- }
-
- public function testPublishesConfigFile()
- {
- $this->artisan('vendor:publish', [
- '--provider' => CaptchavelServiceProvider::class
- ]);
-
- $this->assertFileExists(config_path('captchavel.php'));
- $this->assertFileIsReadable(config_path('captchavel.php'));
- $this->assertFileEquals(config_path('captchavel.php'), __DIR__ . '/../config/captchavel.php');
- $this->assertTrue(unlink(config_path('captchavel.php')));
- }
-
- public function testRegistersMacros()
- {
- \DarkGhostHunter\Captchavel\Facades\ReCaptcha::shouldReceive('isHuman')
- ->once()
- ->andReturnTrue();
-
- \DarkGhostHunter\Captchavel\Facades\ReCaptcha::shouldReceive('isRobot')
- ->once()
- ->andReturnFalse();
-
- $this->assertTrue(Request::isHuman());
- $this->assertFalse(Request::isRobot());
- }
-
-}