Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

All notable changes to `ossycodes/friendlycaptcha` will be documented in this file

## 2.0.0 - 2026-04.08
- Replace the friendly-challenge scripts
```html
<script type="module" src="https://cdn.jsdelivr.net/npm/friendly-challenge@0.9.12/widget.module.min.js" async defer></script>
<script nomodule src="https://cdn.jsdelivr.net/npm/friendly-challenge@0.9.12/widget.min.js" async defer></script>
```
with the new @friendlycaptcha/sdk scripts
```html
<script type="module" src="https://cdn.jsdelivr.net/npm/@friendlycaptcha/sdk@0.2.0/site.min.js"
async defer></script>
<script nomodule src="https://cdn.jsdelivr.net/npm/@friendlycaptcha/sdk@0.2.0/site.compat.min.js"
async defer></script>
```

For more information visit: https://developer.friendlycaptcha.com/docs/v2/guides/upgrading-from-v1/script

## 1.0.0 - 2021-10-21

- initial release
15 changes: 8 additions & 7 deletions lang/de/validation.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
*/

return [
'secret_missing' => 'Sie haben vergessen, den Parameter secret (=API-Schlüssel) hinzuzufügen.',
'secret_invalid' => 'Der von Ihnen angegebene API-Schlüssel war ungültig.',
'solution_missing' => 'Sie haben vergessen, den Parameter secret (=API-Schlüssel) hinzuzufügen.',
'secret_missing' => 'Sie haben vergessen, den Lösungsparameter hinzuzufügen.',
'bad_request' => 'Mit Ihrer Anfrage ist etwas anderes nicht in Ordnung, z. B. ist Ihr Anfragekörper leer.',
'solution_invalid' => 'Die von Ihnen angegebene Lösung war ungültig (vielleicht wurde versucht, das Rätsel zu manipulieren).',
'solution_timeout_or_duplicate' => 'Das Rätsel, für das Sie die Lösung angegeben haben, ist abgelaufen oder wurde bereits verwendet.',
'auth_required' => 'Authentifizierung erforderlich.',
'auth_invalid' => 'Authentifizierung fehlgeschlagen.',
'sitekey_invalid' => 'Der Sitekey ist ungültig.',
'response_missing' => 'Die Captcha-Lösung fehlt.',
'response_invalid' => 'Die Captcha-Lösung ist falsch.',
'response_timeout' => 'Anfrage abgelaufen.',
'response_duplicate' => 'Diese Lösung wurde bereits verwendet.',
'unexpected' => 'Ein unerwarteter Fehler ist aufgetreten.'
];
];
15 changes: 8 additions & 7 deletions lang/en/validation.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
*/

return [
'secret_missing' => 'You forgot to add the secret (=API key) parameter.',
'secret_invalid' => 'The API key you provided was invalid.',
'solution_missing' => 'You forgot to add the secret (=API key) parameter.',
'secret_missing' => 'You forgot to add the solution parameter.',
'bad_request' => 'Something else is wrong with your request, e.g. your request body is empty.',
'solution_invalid' => 'The solution you provided was invalid (perhaps the user tried to tamper with the puzzle).',
'solution_timeout_or_duplicate' => 'The puzzle that the solution was for has expired or has already been used.',
'auth_required' => 'Authentication required.',
'auth_invalid' => 'Authentication failed.',
'sitekey_invalid' => 'The sitekey is invalid.',
'response_missing' => 'The captcha solution is missing.',
'response_invalid' => 'The captcha solution is invalid.',
'response_timeout' => 'Connection timed out.',
'response_duplicate' => 'This solution has already been used.',
'unexpected' => 'An unexpected error occurred.'
];
];
50 changes: 21 additions & 29 deletions src/FriendlyCaptcha.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Ossycodes\FriendlyCaptcha;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;

class FriendlyCaptcha
{
Expand All @@ -20,11 +21,6 @@ class FriendlyCaptcha
*/
protected $sitekey;

/**
* FriendlyCaptcha Puzzle endpoint
*/
protected $puzzle;

/**
* FriendlyCaptcha verify endpoint
*/
Expand All @@ -40,31 +36,23 @@ class FriendlyCaptcha
public $isSuccess = false;

/**
* @var \GuzzleHttp\Client
* @var Client
*/
protected $http;

public function __construct($secret, $sitekey, $puzzle, $verify, $options = [])
public function __construct($secret, $sitekey, $verify, $options = [])
{
$this->secret = $secret;
$this->sitekey = $sitekey;
$this->puzzle = $puzzle;
$this->verify = $verify;
$this->http = new Client($options);
}

public function renderWidgetScripts($option = 'unpkg')
public function renderWidgetScripts(): string
{
if ($option == 'unpkg') {
return <<<EOF
<script type="module" src="https://unpkg.com/friendly-challenge@0.9.9/widget.module.min.js" async defer></script>
<script nomodule src="https://unpkg.com/friendly-challenge@0.9.9/widget.min.js" async defer></script>
EOF;
}

return <<<EOF
<script type="module" src="https://cdn.jsdelivr.net/npm/friendly-challenge@0.9.9/widget.module.min.js" async defer></script>
<script nomodule src="https://cdn.jsdelivr.net/npm/friendly-challenge@0.9.9/widget.min.js" async defer></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/@friendlycaptcha/sdk@0.1.36/site.min.js" async defer></script>
<script nomodule src="https://cdn.jsdelivr.net/npm/@friendlycaptcha/sdk@0.1.36/site.compat.min.js" async defer></script>
EOF;
}

Expand All @@ -83,8 +71,6 @@ public function renderWidget($attributes = [])
*/
protected function prepareAttributes(array $attributes)
{
$attributes['data-puzzle-endpoint'] = $this->puzzle;

$attributes['data-sitekey'] = $this->sitekey;

if (isset($attributes['dark-theme'])) {
Expand Down Expand Up @@ -129,8 +115,9 @@ protected function buildAttributes(array $attributes)
* @param string $solution
*
* @return bool
* @throws GuzzleException
*/
public function verifyRequest($solution)
public function verifyRequest(string $solution)
{
return $this->verifyResponse(
$solution,
Expand All @@ -143,18 +130,21 @@ public function verifyRequest($solution)
* @param string $solution
*
* @return self
* @throws GuzzleException
*/
public function verifyResponse($solution)
public function verifyResponse(string $solution)
{
if (empty($solution)) {
return false;
}

$verifyResponse = $this->sendRequestVerify([
'solution' => $solution,
'secret' => $this->secret,
'sitekey' => $this->sitekey,
]);
$verifyResponse = $this->sendRequestVerify(
['X-API-Key' => $this->secret],
[
'response' => $solution,
'sitekey' => $this->sitekey,
]
);

if (isset($verifyResponse['success']) && $verifyResponse['success'] === true) {
$this->isSuccess = true;
Expand All @@ -178,13 +168,15 @@ public function verifyResponse($solution)
/**
* Send verify request.
*
* @param array $headers
* @param array $data
*
* @return array
* @throws GuzzleException
*/
protected function sendRequestVerify(array $data = [])
protected function sendRequestVerify(array $headers = [], array $data = []): array
{
$response = $this->http->request('POST', $this->verify, [
'headers' => $headers,
'form_params' => $data,
]);

Expand Down
16 changes: 3 additions & 13 deletions src/FriendlyCaptchaServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,10 @@ protected function bootConfig()
*/
public function bootBladeDirectives()
{
Blade::directive('friendlyCaptchaRenderWidgetScripts', function ($option) {
$option = trim($option, "'");

if (empty($option) || $option == 'unpkg') {
return <<<EOF
<script type="module" src="https://unpkg.com/friendly-challenge@0.9.8/widget.module.min.js" async defer></script>
<script nomodule src="https://unpkg.com/friendly-challenge@0.9.8/widget.min.js" async defer></script>
EOF;
}

Blade::directive('friendlyCaptchaRenderWidgetScripts', function () {
return <<<EOF
<script type="module" src="https://cdn.jsdelivr.net/npm/friendly-challenge@0.9.8/widget.module.min.js" async defer></script>
<script nomodule src="https://cdn.jsdelivr.net/npm/friendly-challenge@0.9.8/widget.min.js" async defer></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/@friendlycaptcha/sdk@0.1.36/site.min.js" async defer></script>
<script nomodule src="https://cdn.jsdelivr.net/npm/@friendlycaptcha/sdk@0.1.36/site.compat.min.js" async defer></script>
EOF;
});
}
Expand Down Expand Up @@ -99,7 +90,6 @@ public function register()
return new FriendlyCaptcha(
$app['config']['friendlycaptcha.secret'],
$app['config']['friendlycaptcha.sitekey'],
$app['config']['friendlycaptcha.puzzle_endpoint'],
$app['config']['friendlycaptcha.verify_endpoint'],
$app['config']['friendlycaptcha.options']
);
Expand Down
30 changes: 14 additions & 16 deletions src/Rules/FriendlyCaptcha.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,24 +45,22 @@ public function message()
protected function mapErrorCodeToMessage(string $code): string
{
switch ($code) {
case "secret_missing":
return __('validation.secret_missing');
break;
case "secret_invalid":
return __('validation.secret_invalid');
break;
case "solution_missing":
return __('validation.solution_missing');
break;
case "auth_required":
return __('validation.auth_required');
case "auth_invalid":
return __('validation.auth_invalid');
case "sitekey_invalid":
return __('validation.sitekey_invalid');
case "response_missing":
return __('validation.response_missing');
case "response_invalid":
return __('validation.response_invalid');
case "response_timeout":
return __('validation.response_timeout');
case "response_duplicate":
return __('validation.response_duplicate');
case "bad_request":
return __('validation.bad_request');
break;
case "solution_invalid":
return __('validation.solution_invalid');
break;
case "solution_timeout_or_duplicate":
return __('validation.solution_timeout_or_duplicate');
break;
default:
return __('validation.unexpected');
}
Expand Down
3 changes: 1 addition & 2 deletions src/config/friendlycaptcha.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
return [
'secret' => env('FRIENDLY_CAPTCHA_SECRET'),
'sitekey' => env('FRIENDLY_CAPTCHA_SITEKEY'),
'puzzle_endpoint' => env('FRIENDLY_CAPTCHA_PUZZLE_ENDPOINT', 'https://api.friendlycaptcha.com/api/v1/puzzle'),
'verify_endpoint' => env('FRIENDLY_CAPTCHA_VERIFY_ENDPOINT', 'https://api.friendlycaptcha.com/api/v1/siteverify'),
'verify_endpoint' => env('FRIENDLY_CAPTCHA_VERIFY_ENDPOINT', 'https://global.frcapi.com/api/v2/captcha/siteverify'), //see https://developer.friendlycaptcha.com/docs/v2/guides/upgrading-from-v1/backend-integration
'options' => [
'timeout' => 30,
'http_errors' => false,
Expand Down
26 changes: 6 additions & 20 deletions tests/FriendlyCaptchaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,46 +22,32 @@ public function setUp(): void
{
parent::setUp();

$this->captcha = new FriendlyCaptcha('{secret-key}', '{site-key}', 'https://api.friendlycaptcha.com/api/v1/puzzle', 'https://api.friendlycaptcha.com/api/v1/siteverify');
$this->captcha = new FriendlyCaptcha('{secret-key}', '{site-key}', 'https://global.frcapi.com/api/v2/captcha/siteverify');
}

/**
* @test
*/
public function it_can_render_unpkg_widget_script_correctly()
public function it_can_render_jsdelivr_widget_script_correctly()
{
$this->assertTrue($this->captcha instanceof FriendlyCaptcha);

$expectedScriptOne = '<script type="module" src="https://unpkg.com/friendly-challenge@0.9.9/widget.module.min.js" async defer></script>';
$expectedScriptTwo = '<script nomodule src="https://unpkg.com/friendly-challenge@0.9.9/widget.min.js" async defer></script>';
$expectedScriptOne = '<script type="module" src="https://cdn.jsdelivr.net/npm/@friendlycaptcha/sdk@0.1.36/site.min.js" async defer></script>';
$expectedScriptTwo = '<script nomodule src="https://cdn.jsdelivr.net/npm/@friendlycaptcha/sdk@0.1.36/site.compat.min.js" async defer></script>';

$this->assertStringContainsString($expectedScriptOne, $this->captcha->renderWidgetScripts());
$this->assertStringContainsString($expectedScriptTwo, $this->captcha->renderWidgetScripts());
}

/**
* @test
*/
public function it_can_render_jsdelivr_widget_script_correctly()
{
$this->assertTrue($this->captcha instanceof FriendlyCaptcha);

$expectedScriptOne = '<script type="module" src="https://cdn.jsdelivr.net/npm/friendly-challenge@0.9.9/widget.module.min.js" async defer></script>';
$expectedScriptTwo = '<script nomodule src="https://cdn.jsdelivr.net/npm/friendly-challenge@0.9.9/widget.min.js" async defer></script>';

$this->assertStringContainsString($expectedScriptOne, $this->captcha->renderWidgetScripts('jsdelivr'));
$this->assertStringContainsString($expectedScriptTwo, $this->captcha->renderWidgetScripts('jsdelivr'));
}

/**
* @test
*/
public function it_can_render_widget_correctly()
{
$this->assertTrue($this->captcha instanceof FriendlyCaptcha);

$expectedWidget = '<div data-puzzle-endpoint="https://api.friendlycaptcha.com/api/v1/puzzle" data-sitekey="{site-key}" class="frc-captcha" data-lang="en"></div>';
$expectedWidgetWithCustomAttributes = '<div data-puzzle-endpoint="https://api.friendlycaptcha.com/api/v1/puzzle" data-sitekey="{site-key}" class="frc-captcha dark"></div>';
$expectedWidget = '<div data-sitekey="{site-key}" class="frc-captcha" data-lang="en"></div>';
$expectedWidgetWithCustomAttributes = '<div data-sitekey="{site-key}" class="frc-captcha dark"></div>';

$this->assertEquals($expectedWidget, $this->captcha->renderWidget());
$this->assertEquals($expectedWidgetWithCustomAttributes, $this->captcha->renderWidget(['dark-theme' => true]));
Expand Down