has('operator.2fa.id'), 403); } public function verify(): void { $operator = Operator::find(session('operator.2fa.id')); abort_if($operator === null, 403); $key = 'operator-2fa:'.$operator->id.'|'.request()->ip(); if (RateLimiter::tooManyAttempts($key, 5)) { throw ValidationException::withMessages([ 'code' => __('auth.throttle', ['seconds' => RateLimiter::availableIn($key)]), ]); } if (! $this->passes($operator)) { RateLimiter::hit($key, 60); $this->reset('code', 'recoveryCode'); throw ValidationException::withMessages(['code' => __('auth.twofa_invalid')]); } RateLimiter::clear($key); $remember = (bool) session('operator.2fa.remember', false); session()->forget(['operator.2fa.id', 'operator.2fa.remember']); OperatorLogin::completeLogin($operator, $remember); $this->redirect(AdminArea::home(), navigate: false); } private function passes(Operator $operator): bool { if ($this->usingRecovery) { $codes = json_decode(decrypt($operator->two_factor_recovery_codes), true) ?: []; if (! in_array($this->recoveryCode, $codes, true)) { return false; } // A recovery code is single use — leaving it valid turns a one-time // escape hatch into a second password. $operator->forceFill([ 'two_factor_recovery_codes' => encrypt(json_encode(array_values( array_diff($codes, [$this->recoveryCode]) ))), ])->save(); return true; } return app(TwoFactorAuthenticationProvider::class) ->verify(decrypt($operator->two_factor_secret), $this->code); } public function render() { return view('livewire.auth.operator-two-factor-challenge'); } }