Skip to content

Commit

Permalink
Merge pull request #20 from pinkary-project/refactor/move-recaptcha-t…
Browse files Browse the repository at this point in the history
…o-rule

refactor: move `Recaptcha` to be a rule.
  • Loading branch information
nunomaduro authored Mar 21, 2024
2 parents dffb247 + 8ab41e6 commit 387d192
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 46 deletions.
14 changes: 3 additions & 11 deletions app/Http/Controllers/Auth/RegisteredUserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

use App\Jobs\DownloadUserAvatar;
use App\Models\User;
use App\Rules\Recaptcha;
use App\Rules\Username;
use App\Rules\ValidTimezone;
use App\Services\Recaptcha;
use Illuminate\Auth\Events\Registered;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
Expand All @@ -32,25 +32,17 @@ public function create(): View
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request, Recaptcha $recaptcha): RedirectResponse
public function store(Request $request): RedirectResponse
{
$request->validate([
'name' => ['required', 'string', 'max:255'],
'username' => ['required', 'string', 'min:4', 'max:50', 'unique:'.User::class, new Username],
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class],
'timezone' => ['required', 'string', 'max:255', new ValidTimezone],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
'g-recaptcha-response' => app()->environment('production') ? ['required', 'string'] : [],
'g-recaptcha-response' => ['required', new Recaptcha($request->ip())],
]);

$ipAddress = $request->ip();

assert(is_string($ipAddress));

if (app()->environment('production') && ! $recaptcha->verify($ipAddress, $request->string('g-recaptcha-response')->value())) {
return back()->withErrors(['g-recaptcha-response' => 'The recaptcha response was invalid.']); // @codeCoverageIgnore
}

$user = User::create([
'name' => $request->name,
'email' => $request->email,
Expand Down
21 changes: 0 additions & 21 deletions app/Providers/RecaptchaServiceProvider.php

This file was deleted.

30 changes: 23 additions & 7 deletions app/Services/Recaptcha.php → app/Rules/Recaptcha.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,50 @@

declare(strict_types=1);

namespace App\Services;
namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Http;
use Symfony\Component\HttpFoundation\IpUtils;

final readonly class Recaptcha
final readonly class Recaptcha implements ValidationRule
{
/**
* The recaptcha URL.
*/
private const string URL = 'https://www.google.com/recaptcha/api/siteverify';

/**
* Create a new recaptcha instance.
* Create a new rule instance.
*/
public function __construct(
private string $secret,
) {
public function __construct(private ?string $ip)
{
//
}

/**
* Run the validation rule.
*
* @param Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
assert(is_string($value));
assert(is_string($this->ip));

if (! $this->verify($this->ip, $value)) {
$fail(__('The recaptcha response was invalid.'));
}
}

/**
* Verify the recaptcha response.
*/
public function verify(string $ipAddress, string $response): bool
{
$payload = [
'secret' => $this->secret,
'secret' => config()->string('services.recaptcha.secret'),
'response' => $response,
'remoteip' => IpUtils::anonymize($ipAddress),
];
Expand Down
1 change: 0 additions & 1 deletion bootstrap/providers.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,4 @@
return [
App\Providers\AppServiceProvider::class,
App\Providers\PulseServiceProvider::class,
App\Providers\RecaptchaServiceProvider::class,
];
8 changes: 8 additions & 0 deletions tests/Http/Register/CreateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
declare(strict_types=1);

use App\Models\User;
use Illuminate\Support\Facades\Http;

test('registration screen can be rendered', function () {
$response = $this->get('/register');
Expand All @@ -12,13 +13,20 @@
});

test('new users can register', function () {
Http::fake([
'https://www.google.com/recaptcha/api/siteverify' => Http::response([
'success' => true,
]),
]);

$response = $this->post('/register', [
'name' => 'Test User',
'username' => 'testuser',
'email' => '[email protected]',
'password' => 'm@9v_.*.XCN',
'password_confirmation' => 'm@9v_.*.XCN',
'timezone' => 'UTC',
'g-recaptcha-response' => 'valid',
]);

$this->assertAuthenticated();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,37 @@

declare(strict_types=1);

use App\Services\Recaptcha;
use App\Rules\Recaptcha;
use Illuminate\Support\Facades\Http;

it('verifies the recaptcha response', function () {
$recaptcha = new Recaptcha('secret');
$rule = new Recaptcha('127.0.0.1');

$response = Http::fake([
'https://www.google.com/recaptcha/api/siteverify' => Http::response([
'success' => true,
]),
]);

expect($recaptcha->verify('127.0.0.1', 'response'))->toBeTrue();
$fail = fn (string $errorMessage) => $this->fail($errorMessage);

$rule->validate('g-recaptcha-response', 'valid', $fail);

expect(true)->toBeTrue();
});

it('does not verify the recaptcha response', function () {
$recaptcha = new Recaptcha('secret');
$rule = new Recaptcha('127.0.0.1');

$response = Http::fake([
'https://www.google.com/recaptcha/api/siteverify' => Http::response([
'success' => false,
]),
]);

expect($recaptcha->verify('127.0.0.1', 'response'))->toBeFalse();
});
$fail = fn (string $errorMessage) => $this->fail($errorMessage);

$rule->validate('g-recaptcha-response', 'valid', $fail);

expect(true)->toBeFalse();
})->fails();

0 comments on commit 387d192

Please sign in to comment.