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.'); } } }