requireConfirmedPassword(); app(EnableTwoFactorAuthentication::class)(Auth::guard('operator')->user()); } /** Accept a code from the app, which is what actually turns it on. */ public function confirmTwoFactor(): void { $this->requireConfirmedPassword(); $operator = Auth::guard('operator')->user(); try { app(ConfirmTwoFactorAuthentication::class)( $operator, $this->twoFactorCode, ); } catch (ValidationException $e) { $this->addError('twoFactorCode', $e->errors()['code'][0] ?? __('two_factor_setup.code_wrong')); return; } $this->twoFactorCode = ''; // Shown once, here, because this is the only moment they exist in a // form anyone will read. They stay retrievable afterwards, but nobody // writes down what they were not shown. $this->recoveryCodes = json_decode(decrypt($operator->two_factor_recovery_codes), true); $this->dispatch('notify', message: __('two_factor_setup.on')); } public function regenerateRecoveryCodes(): void { $this->requireConfirmedPassword(); $operator = Auth::guard('operator')->user(); app(GenerateNewRecoveryCodes::class)($operator); $this->recoveryCodes = json_decode(decrypt($operator->refresh()->two_factor_recovery_codes), true); } public function disableTwoFactor(): void { $this->requireConfirmedPassword(); app(DisableTwoFactorAuthentication::class)(Auth::guard('operator')->user()); $this->recoveryCodes = null; $this->dispatch('notify', message: __('two_factor_setup.off')); } /** * Every two-factor action goes through this. * * Server-side, on each call, and not merely by hiding the buttons: a * Livewire action is reachable by anyone who can post to /livewire/update, * and "the form was not on screen" has never stopped anybody. */ private function requireConfirmedPassword(): void { abort_unless($this->passwordRecentlyConfirmed(), 403); } public function render() { $operator = Auth::guard('operator')->user(); return view('livewire.admin.two-factor-setup', [ // Never the secret itself — only whether it exists, and the SVG // Fortify renders from it. The secret in a Livewire property would // travel to the browser and back in the component snapshot. 'twoFactorPending' => $operator->two_factor_secret !== null && $operator->two_factor_confirmed_at === null, 'twoFactorOn' => $operator->two_factor_confirmed_at !== null, 'twoFactorQr' => $operator->two_factor_secret !== null && $operator->two_factor_confirmed_at === null ? $operator->twoFactorQrCodeSvg() : null, 'passwordConfirmed' => $this->passwordRecentlyConfirmed(), ]); } }