pollId = $pollId; } public function check(Ledger $ledger): void { $this->validate(['code' => ['required', 'string', 'max:40']], [], ['code' => __('Receipt code')]); // Codes cannot be guessed (about 99 bits), but checks are limited anyway (security audit F6) $limiterKey = 'poll-receipt-check:' . (auth('web')->id() ?? request()->ip()); if (RateLimiter::tooManyAttempts($limiterKey, 20)) { $this->addError('code', __('Too many checks. Please try again in a minute.')); return; } RateLimiter::hit($limiterKey, 60); $poll = Poll::findOrFail($this->pollId); $this->result = null; $this->notFound = false; $found = $poll->verification !== Poll::VERIFICATION_OFF ? $ledger->lookupReceipt($poll, $this->code) : null; if (!$found) { $this->notFound = true; return; } $entry = $found['entry']; $questions = $poll->questions()->get()->keyBy('id'); $registry = app(QuestionTypeRegistry::class); // Texts are in poll_answers only while this ballot is the voter's current one $texts = []; $response = PollResponse::where('poll_id', $poll->id)->where('ledger_sequence', $entry->sequence)->first(); if ($response) { $texts = PollAnswer::where('poll_response_id', $response->id)->pluck('text', 'poll_question_id')->all(); } $answers = []; foreach ($entry->payload['answers'] ?? [] as $answer) { $question = $questions->get($answer['question_id']); if (!$question || !$registry->has($question->type)) { continue; } $text = $texts[$question->id] ?? null; $textHash = $answer['text_sha256'] ?? null; $textMatches = $textHash && $text !== null && hash_equals($textHash, hash('sha256', $text)); $shownText = $textMatches ? $text : ($textHash ? '[' . __('text') . ']' : null); $answers[] = [ 'question' => $question->labelFor(App::getLocale()), 'answer' => $question->questionType()->answerToText($answer['value'] ?? null, $shownText, $question->settings ?? [], App::getLocale()), 'text_checked' => $textMatches, ]; } $this->result = [ 'sequence' => $entry->sequence, 'status' => $found['status'], 'recorded_at' => $entry->recorded_at, 'answers' => $answers, ]; } public function render() { return view('livewire.polls.receipt-check'); } }