Replace captcha package with own implementation
This commit is contained in:
129
app/Rules/ValidCaptcha.php
Normal file
129
app/Rules/ValidCaptcha.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use AltchaOrg\Altcha\Algorithm\Pbkdf2;
|
||||
use AltchaOrg\Altcha\Altcha;
|
||||
use AltchaOrg\Altcha\Challenge;
|
||||
use AltchaOrg\Altcha\ChallengeParameters;
|
||||
use AltchaOrg\Altcha\Payload;
|
||||
use AltchaOrg\Altcha\Solution;
|
||||
use AltchaOrg\Altcha\VerifySolutionOptions;
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
|
||||
/**
|
||||
* Validation rule to verify captcha solution.
|
||||
*/
|
||||
class ValidCaptcha implements ValidationRule
|
||||
{
|
||||
/**
|
||||
* Altcha instance.
|
||||
*/
|
||||
protected Altcha $altcha;
|
||||
|
||||
/**
|
||||
* Pbkdf2 algorithm instance.
|
||||
*/
|
||||
protected Pbkdf2 $pbkdf2;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->pbkdf2 = new Pbkdf2;
|
||||
$this->altcha = new Altcha(
|
||||
hmacSignatureSecret: config('captcha.hmac_key'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse payload and return the decoded data as an array.
|
||||
*/
|
||||
private function parsePayload(string $value): ?array
|
||||
{
|
||||
$decoded = base64_decode($value, true);
|
||||
if ($decoded === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$payload = json_decode($decoded, true);
|
||||
if (! is_array($payload)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if payload has required fields.
|
||||
*/
|
||||
private function verifyFields(array $payload): bool
|
||||
{
|
||||
if (! isset($payload['challenge'], $payload['solution'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! is_array($payload['challenge']) || ! is_array($payload['solution'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Challenge object from challenge data.
|
||||
*/
|
||||
private function createChallenge(array $challengeData): Challenge
|
||||
{
|
||||
return new Challenge(
|
||||
ChallengeParameters::fromArray($challengeData['parameters'] ?? []),
|
||||
$challengeData['signature'] ?? null,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Solution object from solution data.
|
||||
*/
|
||||
private function createSolution(array $solutionData): Solution
|
||||
{
|
||||
return new Solution(
|
||||
counter: (int) ($solutionData['counter'] ?? 0),
|
||||
derivedKey: (string) ($solutionData['derivedKey'] ?? ''),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the validation rule.
|
||||
*
|
||||
* @param \Closure(string, ?string=): \Illuminate\Translation\PotentiallyTranslatedString $fail
|
||||
*/
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
$payload = $this->parsePayload($value);
|
||||
if (! $payload) {
|
||||
$fail('Invalid captcha.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->verifyFields($payload)) {
|
||||
$fail('Invalid captcha.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$challenge = $this->createChallenge($payload['challenge']);
|
||||
$solution = $this->createSolution($payload['solution']);
|
||||
|
||||
$result = $this->altcha->verifySolution(new VerifySolutionOptions(
|
||||
algorithm: $this->pbkdf2,
|
||||
payload: new Payload($challenge, $solution),
|
||||
));
|
||||
|
||||
if (! $result->verified) {
|
||||
$fail('Invalid captcha.');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user