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

Commit

Permalink
Adds ability to use default auth guard (#50)
Browse files Browse the repository at this point in the history
* Adds tests for authenticated users.

* Updates code examples on the README.

* Updates README.md

* Fixes wrong variable name

* Fixed Generic user not being instanced properly

* Fixes tests for score challenges

* Fixes guard not configured

* Fixes Captchavel mock for auth tests.

* Fixes authentication tests

* Fixes route controller test

* More test fixes

* More tests fixes

* Fixes helper not taking into account default guard
  • Loading branch information
DarkGhostHunter authored Jan 5, 2022
1 parent df43d89 commit a1312a9
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 12 deletions.
43 changes: 34 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,19 +179,39 @@ Route::post('comment', [CommentController::class, 'create'])
#### Bypassing on authenticated users

Sometimes you may want to bypass reCAPTCHA checks when there is an authenticated user, or automatically receive it as a "human" on score-driven challenges. While in your frontend you can programmatically disable reCAPTCHA when the user is authenticated, on the routes you can specify the guards to bypass using `except()`.
Sometimes you may want to bypass reCAPTCHA checks when there is an authenticated user, or automatically receive it as a "human" on score-driven challenges, specially on recurrent actions or when the user already completes a challenge (like on logins).

To exclude authenticated user you can use `forGuests()`, and specify the guards if necessary.

```php
use App\Http\Controllers\CommentController;
use App\Http\Controllers\MessageController;
use DarkGhostHunter\Captchavel\ReCaptcha;
use Illuminate\Support\Facades\Route

// Don't challenge users authenticated on the default (web) guard.
Route::post('message/send', [MessageController::class, 'send'])
->middleware(ReCaptcha::invisible()->except('user'));
->middleware(ReCaptcha::invisible()->forGuests());

// Don't challenge users authenticated on the "admin" and "moderator" guards.
Route::post('comment/store', [CommentController::class, 'store'])
->middleware(ReCaptcha::score(0.7)->action('comment.store')->except('admin', 'moderator'));
->middleware(ReCaptcha::score(0.7)->action('comment.store')->forGuests('admin', 'moderator'));
```

Then, in your blade files, you can easily skip the challenge with the `@guest` or `@auth` directives.

```blade
<form id="comment" method="post">
<textarea name="body"></textarea>
@auth
<button type="submit">Post comment</button>
@else
<button class="g-recaptcha" data-sitekey="{{ captchavel('invisible') }}" data-callback="onSubmit">
Post comment
</button>
@endauth
</form>
```

#### Faking reCAPTCHA scores
Expand All @@ -209,10 +229,14 @@ From there, you can fake a robot response by filling the `is_robot` input in you
```blade
<form id="comment" method="post">
<textarea name="body"></textarea>
@env('local', 'testing')
<input type="checkbox" name="is_robot" checked>
@endenv
<button class="g-recaptcha" data-sitekey="{{ captchavel('invisible') }}" data-callback='onSubmit'>Login</button>
<button class="g-recaptcha" data-sitekey="{{ captchavel('invisible') }}" data-callback='onSubmit'>
Post comment
</button>
</form>
```

Expand All @@ -224,11 +248,12 @@ You can use the `captchavel()` helper to output the site key depending on the ch

```blade
<form id='login' method="POST">
<input type="email" name="email">
<input type="password" name="password">
<button class="g-recaptcha" data-sitekey="{{ captchavel('invisible') }}" data-callback='onSubmit'>Login</button>
<br/>
<input type="email" name="email">
<input type="password" name="password">
<button class="g-recaptcha" data-sitekey="{{ captchavel('invisible') }}" data-callback='onSubmit'>
Login
</button>
</form>
```

Expand Down
4 changes: 4 additions & 0 deletions src/Http/Middleware/VerificationHelpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ protected function isGuest(array $guards): bool
{
$auth = auth();

if ($guards === ['null']) {
$guards = [null];
}

foreach ($guards as $guard) {
if ($auth->guard($guard)->check()) {
return false;
Expand Down
19 changes: 16 additions & 3 deletions src/ReCaptcha.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,18 @@ public function input(string $name): static
*/
public function except(string ...$guards): static
{
$this->guards = $guards;
return $this->forGuests(...$guards);
}

/**
* Show the challenge on non-authenticated users.
*
* @param string ...$guards
* @return $this
*/
public function forGuests(string ...$guards): static
{
$this->guards = $guards ?: ['null'];

return $this;
}
Expand Down Expand Up @@ -209,8 +220,10 @@ public function __toString(): string
{
$declaration = $this->getBaseParameters()
->reverse()
->skipUntil(static function (string $parameter): bool {
return $parameter !== 'null';
->unless($this->guards, static function (Collection $parameters): Collection {
return $parameters->skipUntil(static function (string $parameter): bool {
return $parameter !== 'null';
});
})
->reverse()
->implode(',');
Expand Down
109 changes: 109 additions & 0 deletions tests/Http/Middleware/ChallengeMiddlewareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use DarkGhostHunter\Captchavel\Captchavel;
use DarkGhostHunter\Captchavel\Http\ReCaptchaResponse;
use Illuminate\Auth\GenericUser;
use LogicException;
use Orchestra\Testbench\TestCase;
use Tests\CreatesFulfilledResponse;
Expand Down Expand Up @@ -69,6 +70,114 @@ public function test_bypass_if_not_enabled(): void
$this->post('v2/android')->assertOk();
}

public function test_bypasses_if_authenticated_on_default_guard(): void
{
$mock = $this->mock(Captchavel::class);

$mock->expects('isDisabled')->times(3)->andReturnFalse();
$mock->expects('shouldFake')->times(3)->andReturnFalse();
$mock->allows('getChallenge')->never();

$this->actingAs(new GenericUser([]));

$this->app['router']->post('checkbox/auth', [__CLASS__, 'returnResponseIfExists'])
->middleware('recaptcha:checkbox,null,null,null');
$this->app['router']->post('invisible/auth', [__CLASS__, 'returnResponseIfExists'])
->middleware('recaptcha:invisible,null,null,null');
$this->app['router']->post('android/auth', [__CLASS__, 'returnResponseIfExists'])
->middleware('recaptcha:android,null,null,null');

$this->post('/checkbox/auth')->assertOk();
$this->post('/invisible/auth')->assertOk();
$this->post('/android/auth')->assertOk();
}

public function test_bypasses_if_authenticated_on_one_of_given_guard(): void
{
config()->set('auth.guards.api', [
'driver' => 'session',
'provider' => 'users',
]);

$mock = $this->mock(Captchavel::class);

$mock->expects('isDisabled')->times(3)->andReturnFalse();
$mock->expects('shouldFake')->times(3)->andReturnFalse();
$mock->allows('getChallenge')->never();

$this->actingAs(new GenericUser([]), 'api');

$this->app['router']->post('checkbox/auth', [__CLASS__, 'returnResponseIfExists'])
->middleware('recaptcha:checkbox,null,null,web,api');
$this->app['router']->post('invisible/auth', [__CLASS__, 'returnResponseIfExists'])
->middleware('recaptcha:invisible,null,null,web,api');
$this->app['router']->post('android/auth', [__CLASS__, 'returnResponseIfExists'])
->middleware('recaptcha:android,null,null,web,api');

$this->post('/checkbox/auth')->assertOk();
$this->post('/invisible/auth')->assertOk();
$this->post('/android/auth')->assertOk();
}

public function test_error_if_guest(): void
{
$mock = $this->mock(Captchavel::class);

$mock->expects('isDisabled')->times(3)->andReturnFalse();
$mock->expects('shouldFake')->times(3)->andReturnFalse();
$mock->allows('getChallenge')->never();

$this->app['router']->post('checkbox/auth', [__CLASS__, 'returnResponseIfExists'])
->middleware('recaptcha:checkbox,null,null,null');
$this->app['router']->post('invisible/auth', [__CLASS__, 'returnResponseIfExists'])
->middleware('recaptcha:invisible,null,null,null');
$this->app['router']->post('android/auth', [__CLASS__, 'returnResponseIfExists'])
->middleware('recaptcha:android,null,null,null');

$this->post('/checkbox/auth')
->assertSessionHasErrors(Captchavel::INPUT, trans('captchavel::validation.missing'))
->assertRedirect('/');
$this->post('/invisible/auth')
->assertSessionHasErrors(Captchavel::INPUT, trans('captchavel::validation.missing'))
->assertRedirect('/');
$this->post('/android/auth')
->assertSessionHasErrors(Captchavel::INPUT, trans('captchavel::validation.missing'))
->assertRedirect('/');
}

public function test_error_if_guest_on_given_guard(): void
{
config()->set('auth.guards.api', [
'driver' => 'session',
'provider' => 'users',
]);

$mock = $this->mock(Captchavel::class);

$mock->expects('isDisabled')->times(3)->andReturnFalse();
$mock->expects('shouldFake')->times(3)->andReturnFalse();
$mock->allows('getChallenge')->never();

$this->actingAs(new GenericUser([]));

$this->app['router']->post('checkbox/auth', [__CLASS__, 'returnResponseIfExists'])
->middleware('recaptcha:checkbox,null,null,api');
$this->app['router']->post('invisible/auth', [__CLASS__, 'returnResponseIfExists'])
->middleware('recaptcha:invisible,null,null,api');
$this->app['router']->post('android/auth', [__CLASS__, 'returnResponseIfExists'])
->middleware('recaptcha:android,null,null,api');

$this->post('/checkbox/auth')
->assertSessionHasErrors(Captchavel::INPUT, trans('captchavel::validation.missing'))
->assertRedirect('/');
$this->post('/invisible/auth')
->assertSessionHasErrors(Captchavel::INPUT, trans('captchavel::validation.missing'))
->assertRedirect('/');
$this->post('/android/auth')
->assertSessionHasErrors(Captchavel::INPUT, trans('captchavel::validation.missing'))
->assertRedirect('/');
}

public function test_success_when_enabled_and_fake(): void
{
config(['captchavel.enable' => true]);
Expand Down
57 changes: 57 additions & 0 deletions tests/Http/Middleware/ScoreMiddlewareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use DarkGhostHunter\Captchavel\CaptchavelFake;
use DarkGhostHunter\Captchavel\Http\ReCaptchaResponse;
use DarkGhostHunter\Captchavel\ReCaptcha;
use Illuminate\Auth\GenericUser;
use Illuminate\Foundation\Auth\User;
use Illuminate\Http\Client\Factory;
use Illuminate\Http\Request;
Expand Down Expand Up @@ -169,6 +170,62 @@ public function test_uses_custom_input(): void
->assertExactJson(['success' => true, 'score' => 0.7, 'foo' => 'bar']);
}

public function test_fakes_human_score_if_authenticated_in_default_guard(): void
{
$mock = $this->mock(Captchavel::class);

$mock->allows('getChallenge')->never();

$this->actingAs(new GenericUser([]));

$this->app['router']->post('score/auth', [__CLASS__, 'returnSameResponse'])
->middleware('recaptcha.score:0.5,null,null,null');

$this->post('/score/auth')->assertOk();
}

public function test_fakes_human_score_if_authenticated_in_one_of_given_guards(): void
{
config()->set('auth.guards.api', [
'driver' => 'session',
'provider' => 'users',
]);

$mock = $this->mock(Captchavel::class);

$mock->allows('getChallenge')->never();

$this->actingAs(new GenericUser([]), 'api');

$this->app['router']->post('score/auth', [__CLASS__, 'returnSameResponse'])
->middleware('recaptcha.score:0.5,null,null,web,api');

$this->post('/score/auth')->assertOk();
}

public function test_error_if_is_guest(): void
{
config()->set('auth.guards.api', [
'driver' => 'session',
'provider' => 'users',
]);

$mock = $this->mock(Captchavel::class);

$mock->expects('isDisabled')->once()->andReturnFalse();
$mock->expects('shouldFake')->once()->andReturnFalse();
$mock->allows('getChallenge')->never();

$this->actingAs(new GenericUser([]));

$this->app['router']->post('score/auth', [__CLASS__, 'returnSameResponse'])
->middleware('recaptcha.score:0.5,null,null,api');

$this->post('/score/auth')
->assertSessionHasErrors(Captchavel::INPUT, trans('captchavel::validation.missing'))
->assertRedirect('/');
}

public function test_exception_when_token_absent(): void
{
$this->post('v3/default', ['foo' => 'bar'])
Expand Down
8 changes: 8 additions & 0 deletions tests/ReCaptchaMiddlewareHelperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,12 @@ public function test_cast_to_string(): void
static::assertEquals('recaptcha.score:0.7', ReCaptcha::score(0.7)->toString());
static::assertEquals('recaptcha.score:0.7', ReCaptcha::score(0.7)->__toString());
}

public function tests_uses_all_guards_as_exception(): void
{
static::assertEquals('recaptcha:checkbox,null,null,null', ReCaptcha::checkbox()->forGuests()->toString());
static::assertEquals('recaptcha:invisible,null,null,null', ReCaptcha::invisible()->forGuests()->toString());
static::assertEquals('recaptcha:android,null,null,null', ReCaptcha::android()->forGuests()->toString());
static::assertEquals('recaptcha.score:0.5,null,null,null', ReCaptcha::score()->forGuests()->toString());
}
}

0 comments on commit a1312a9

Please sign in to comment.