Skip to content

Commit 6981f3e

Browse files
authored
Merge pull request #291 from dotkernel/issue-290
Refactored ReCaptcha logic
2 parents a757932 + 2487e12 commit 6981f3e

6 files changed

Lines changed: 115 additions & 6 deletions

File tree

composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
},
4545
"require": {
4646
"php": "^7.4 || ~8.0.0 || ~8.1.0",
47+
"ext-curl": "*",
48+
"ext-json": "*",
4749
"dotkernel/dot-annotated-services": "^3.2.1",
4850
"dotkernel/dot-authorization": "^3.2.0",
4951
"dotkernel/dot-controller": "^3.2.1",

config/autoload/local.php.dist

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,10 @@ return [
7676
'base_dir' => getcwd() . '/data/language',
7777
],
7878
'recaptcha' => [
79+
'scoreThreshold' => (float) 0.5,
7980
'siteKey' => '',
80-
'secretKey' => ''
81+
'secretKey' => '',
82+
'verifyUrl' => 'https://www.google.com/recaptcha/api/siteverify'
8183
],
8284

8385
'rememberMe' => [

src/App/src/ConfigProvider.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@
44

55
namespace Frontend\App;
66

7-
use ContainerInteropDoctrine\EntityManagerFactory;
87
use Doctrine\ORM\EntityManager;
98
use Doctrine\ORM\EntityManagerInterface;
109
use Dot\AnnotatedServices\Factory\AnnotatedServiceFactory;
1110
use Frontend\App\Controller\LanguageController;
1211
use Frontend\App\Factory\EntityListenerResolverFactory;
1312
use Frontend\App\Resolver\EntityListenerResolver;
13+
use Frontend\App\Service\RecaptchaService;
1414
use Frontend\App\Service\TranslateService;
1515
use Frontend\App\Service\TranslateServiceInterface;
1616
use Mezzio\Application;
17+
use Roave\PsrContainerDoctrine\EntityManagerFactory;
1718

1819
/**
1920
* The configuration provider for the App module
@@ -56,6 +57,7 @@ public function getDependencies(): array
5657
EntityListenerResolver::class => EntityListenerResolverFactory::class,
5758
TranslateService::class => AnnotatedServiceFactory::class,
5859
LanguageController::class => AnnotatedServiceFactory::class,
60+
RecaptchaService::class => AnnotatedServiceFactory::class,
5961
],
6062
'aliases' => [
6163
EntityManager::class => 'doctrine.entity_manager.orm_default',
@@ -68,7 +70,7 @@ public function getDependencies(): array
6870
/**
6971
* @return array
7072
*/
71-
public function getDoctrineConfig()
73+
public function getDoctrineConfig(): array
7274
{
7375
return [
7476
'configuration' => [
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
3+
4+
namespace Frontend\App\Service;
5+
6+
use Dot\AnnotatedServices\Annotation\Inject;
7+
use Fig\Http\Message\StatusCodeInterface;
8+
use InvalidArgumentException;
9+
10+
/**
11+
* Class RecaptchaService
12+
* @package Frontend\App\Service
13+
*/
14+
class RecaptchaService
15+
{
16+
private array $config;
17+
18+
private string $response;
19+
20+
/**
21+
* RecaptchaService constructor.
22+
* @param array $config
23+
*
24+
* @Inject({"config.recaptcha"})
25+
*/
26+
public function __construct(array $config)
27+
{
28+
$this->validateConfig($config);
29+
30+
$this->config = $config;
31+
}
32+
33+
/**
34+
* @param string $response
35+
* @return $this
36+
*/
37+
public function setResponse(string $response): self
38+
{
39+
$this->response = $response;
40+
41+
return $this;
42+
}
43+
44+
/**
45+
* @return bool
46+
*/
47+
public function isValid(): bool
48+
{
49+
if (! isset($this->response)) {
50+
throw new InvalidArgumentException('Recaptcha response not initialized.');
51+
}
52+
53+
$data = [
54+
'secret' => $this->config['secretKey'],
55+
'response' => $this->response,
56+
];
57+
58+
$curl = curl_init();
59+
60+
curl_setopt($curl, CURLOPT_POST, true);
61+
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
62+
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
63+
curl_setopt($curl, CURLOPT_URL, $this->config['verifyUrl']);
64+
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data));
65+
66+
$response = curl_exec($curl);
67+
$response = json_decode($response, true);
68+
$statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
69+
70+
if ($statusCode !== StatusCodeInterface::STATUS_OK) {
71+
return false;
72+
}
73+
74+
$success = $response['success'] ?? false;
75+
$score = $response['score'] ?? 0;
76+
77+
return $success && $score > $this->config['scoreThreshold'];
78+
}
79+
80+
private function validateConfig(array $config): void
81+
{
82+
$keysToValidate = ['siteKey', 'secretKey', 'verifyUrl', 'scoreThreshold'];
83+
foreach ($keysToValidate as $key) {
84+
if (empty($config[$key])) {
85+
throw new InvalidArgumentException("Invalid `{$key}` provided.");
86+
}
87+
}
88+
89+
if (! is_float($config['scoreThreshold']) || $config['scoreThreshold'] < 0 || $config['scoreThreshold'] > 1) {
90+
throw new InvalidArgumentException(
91+
"Invalid `{$key}` provided. The value must be a float between 0.0 and 1.0"
92+
);
93+
}
94+
}
95+
}

src/Contact/src/Controller/ContactController.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Dot\FlashMessenger\FlashMessenger;
1010
use Dot\Mail\Exception\MailException;
1111
use Fig\Http\Message\RequestMethodInterface;
12+
use Frontend\App\Service\RecaptchaService;
1213
use Frontend\Contact\Form\ContactForm;
1314
use Frontend\Contact\Service\MessageService;
1415
use Frontend\Plugin\FormsPlugin;
@@ -31,6 +32,9 @@ class ContactController extends AbstractActionController
3132
/** @var MessageService $messageService */
3233
protected MessageService $messageService;
3334

35+
/** @var RecaptchaService $recaptchaService */
36+
protected RecaptchaService $recaptchaService;
37+
3438
/** @var AuthenticationServiceInterface $authenticationService */
3539
protected AuthenticationServiceInterface $authenticationService;
3640

@@ -46,13 +50,16 @@ class ContactController extends AbstractActionController
4650
/**
4751
* UserController constructor.
4852
* @param MessageService $messageService
53+
* @param RecaptchaService $recaptchaService
4954
* @param RouterInterface $router
5055
* @param TemplateRendererInterface $template
5156
* @param AuthenticationService $authenticationService
5257
* @param FlashMessenger $messenger
5358
* @param FormsPlugin $forms
59+
* @param array $config
5460
* @Inject({
5561
* MessageService::class,
62+
* RecaptchaService::class,
5663
* RouterInterface::class,
5764
* TemplateRendererInterface::class,
5865
* AuthenticationService::class,
@@ -63,6 +70,7 @@ class ContactController extends AbstractActionController
6370
*/
6471
public function __construct(
6572
MessageService $messageService,
73+
RecaptchaService $recaptchaService,
6674
RouterInterface $router,
6775
TemplateRendererInterface $template,
6876
AuthenticationService $authenticationService,
@@ -71,6 +79,7 @@ public function __construct(
7179
array $config = []
7280
) {
7381
$this->messageService = $messageService;
82+
$this->recaptchaService = $recaptchaService;
7483
$this->router = $router;
7584
$this->template = $template;
7685
$this->authenticationService = $authenticationService;
@@ -94,9 +103,9 @@ public function formAction(): ResponseInterface
94103
$data = $request->getParsedBody();
95104
//check recaptcha
96105
if (isset($data['g-recaptcha-response'])) {
97-
if (!$this->messageService->recaptchaIsValid($data['g-recaptcha-response'])) {
106+
if (! $this->recaptchaService->setResponse($data['g-recaptcha-response'])->isValid()) {
98107
unset($data['g-recaptcha-response']);
99-
$this->messenger->addError('Wrong recaptcha');
108+
$this->messenger->addError('Captcha verification failed. Please try again.');
100109
return new RedirectResponse($request->getUri(), 303);
101110
}
102111
} else {

src/Contact/src/Service/MessageService.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use Frontend\Contact\Entity\Message;
88
use Frontend\Contact\Repository\MessageRepository;
99
use Doctrine\ORM\EntityManager;
10-
use Doctrine\ORM\EntityNotFoundException;
1110
use Dot\AnnotatedServices\Annotation\Inject;
1211
use Dot\Mail\Exception\MailException;
1312
use Dot\Mail\Service\MailService;

0 commit comments

Comments
 (0)