From a8502b1ab23b58976014fc0fa8793a840dea580d Mon Sep 17 00:00:00 2001 From: Bas van Dinther Date: Fri, 20 Sep 2024 15:41:52 +0200 Subject: [PATCH] [Feature] Show user phone or email before sending OTP (#39) * wip * Fix styling * wip * Fix styling * Add missing period * Only show ask for confirmation when necessary * Fix styling * Fix PHPStan * User should define phone number field * Fix styling * Add missing return types * Fix styling * Fix missing deactivated notification --------- Co-authored-by: Baspa --- README.md | 1 + config/filament-two-factor-auth.php | 1 + resources/lang/en.json | 8 +- resources/lang/nl.json | 8 +- src/Pages/TwoFactor.php | 130 +++++++++++++++++++++++----- 5 files changed, 122 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 56ef520..a619280 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,7 @@ return [ 'sms_service' => null, // For example 'vonage', 'twilio', 'nexmo', etc. 'send_otp_class' => null, + 'phone_number_field' => 'phone', // The field name of the phone number in your user model ]; ``` diff --git a/config/filament-two-factor-auth.php b/config/filament-two-factor-auth.php index 313efba..868519a 100644 --- a/config/filament-two-factor-auth.php +++ b/config/filament-two-factor-auth.php @@ -65,6 +65,7 @@ */ 'sms_service' => null, // For example 'vonage', 'twilio', 'nexmo', etc. 'send_otp_class' => null, + 'phone_number_field' => 'phone', /* |-------------------------------------------------------------------------- diff --git a/resources/lang/en.json b/resources/lang/en.json index fa5ba3a..c8796f5 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -1,5 +1,6 @@ { "Activate": "Activate", + "Activate Two-Factor Authentication": "Activate Two-Factor Authentication", "Add additional security to your account using two factor authentication": "Add additional security to your account using two factor authentication", "Authenticate with your code": "Authenticate with your code", "Authenticator app": "Authenticator app", @@ -15,6 +16,11 @@ "Login": "Login", "Or scan the QR code with your authenticator app": "Or scan the QR code with your authenticator app", "Password Confirmation": "Password Confirmation", + "Phone number": "Phone number", + "email": "email", + "phone": "phone", + "authenticator app": "authenticator app", + "Please confirm this is your :type before continuing.": "Please confirm this is your :type before continuing.", "Recovery code": "Recovery code", "Regenerate": "Regenerate", "Resend": "Resend", @@ -38,4 +44,4 @@ "Your account has been secured with two factor authentication": "Your account has been secured with two factor authentication", "Your security code for :app": "Your security code for :app", "Your security code is": "Your security code is" -} \ No newline at end of file +} diff --git a/resources/lang/nl.json b/resources/lang/nl.json index 7862e2f..d9ea309 100644 --- a/resources/lang/nl.json +++ b/resources/lang/nl.json @@ -1,5 +1,6 @@ { "Activate": "Activeer", + "Activate Two-Factor Authentication": "Activeer tweestapsverificatie", "Add additional security to your account using two factor authentication": "Voeg extra beveiliging toe aan uw account met tweestapsverificatie", "Authenticate with your code": "Verifieer met uw authenticatiecode", "Authenticator app": "Authenticator app", @@ -15,6 +16,11 @@ "Login": "Inloggen", "Or scan the QR code with your authenticator app": "Of scan de QR-code met uw authenticator-app", "Password Confirmation": "Wachtwoordbevestiging", + "Phone number": "Telefoonnummer", + "email": "e-mailadres", + "phone": "telefoonnummer", + "authenticator app": "authenticator app", + "Please confirm this is your :type before continuing.": "Bevestig dat dit uw :type is voordat u doorgaat.", "Recovery code": "Herstelcode", "Regenerate": "Opnieuw genereren", "Resend": "Opnieuw Verzenden", @@ -40,4 +46,4 @@ "Your administrator requires you to enable two-factor authentication.": "Uw beheerder vereist dat u tweestapsverificatie inschakelt.", "Your security code for :app": "Uw beveiligingscode voor :app", "Your security code is": "Uw beveiligingscode is" -} \ No newline at end of file +} diff --git a/src/Pages/TwoFactor.php b/src/Pages/TwoFactor.php index 90b67ce..08dfd7b 100644 --- a/src/Pages/TwoFactor.php +++ b/src/Pages/TwoFactor.php @@ -143,36 +143,119 @@ protected function getForms(): array ]; } + public function translatedType(string $type): string + { + return match ($type) { + TwoFactorType::email->value => __('email'), + TwoFactorType::phone->value => __('phone'), + TwoFactorType::authenticator->value => __('authenticator app'), + default => $type, + }; + } + public function enableAction(): Action { - return Action::make('enable') - ->label(__('Activate')) + $action = Action::make('enable') ->color('primary') - ->action(function ($data) { - $formData = []; + ->label(__('Activate')); + + if (isset($this->twoFactorData['option']) && $this->twoFactorData['option'] !== TwoFactorType::authenticator->value) { + $action->requiresConfirmation() + ->modalHeading(__('Activate Two-Factor Authentication')) + ->modalDescription(__('Please confirm this is your :type before continuing.', [ + 'type' => $this->translatedType($this->twoFactorData['option']), + ])) + ->form(function (): array { + $fields = $this->getConfirmableFields(); + $option = $this->twoFactorData['option']; + + if (isset($fields[$option])) { + $field = $fields[$option]; + + return [ + TextInput::make($field['name']) + ->label($field['label']) + ->default($field['default']) + ->required() + ->rules($field['rules']) + ->inlineLabel(), + ]; + } + + return []; + }); + } - if (isset($data['email'])) { - $formData['email'] = $data['email']; - } + return $action->action(function (array $data): void { + $formData = []; - if ($this->twoFactorData['option']) { - $formData['two_factor_type'] = TwoFactorType::tryFrom($this->twoFactorData['option']); - } + if (isset($data['email'])) { + $formData['email'] = $data['email']; + } - /** @var array{two_factor_type: TwoFactorType|null, email?: mixed} $formData */ - if ( - isset($formData['two_factor_type']) && - ($formData['two_factor_type'] === TwoFactorType::email || $formData['two_factor_type'] === TwoFactorType::phone) - ) { - $this->showQrCode = false; - } else { - $this->showQrCode = true; - } + if ($this->twoFactorData['option']) { + $formData['two_factor_type'] = TwoFactorType::tryFrom($this->twoFactorData['option']); + } - $this->user->update($formData); + /** @var array{two_factor_type: TwoFactorType|null, email?: mixed} $formData */ + if ( + isset($formData['two_factor_type']) && + ($formData['two_factor_type'] === TwoFactorType::email || $formData['two_factor_type'] === TwoFactorType::phone) + ) { + $this->showQrCode = false; + } else { + $this->showQrCode = true; + } - $this->enableTwoFactorAuthentication(app(EnableTwoFactorAuthentication::class)); - }); + $this->user->update($formData); + + $this->enableTwoFactorAuthentication(app(EnableTwoFactorAuthentication::class)); + }); + } + + /** @return array */ + private function getConfirmableFields(): array + { + $confirmableOptions = config('filament-two-factor-auth.options', [ + TwoFactorType::email, + TwoFactorType::phone, + ]); + + $fields = []; + + foreach ($confirmableOptions as $option) { + switch ($option) { + case TwoFactorType::authenticator: + $fields[$option->value] = [ + 'name' => 'authenticator', + 'label' => __('Authenticator'), + 'default' => null, // Set default value if needed + 'rules' => ['required'], + ]; + + break; + case TwoFactorType::email: + $fields[$option->value] = [ + 'name' => 'email', + 'label' => __('Email'), + 'default' => $this->user->email, + 'rules' => ['required', 'email'], + ]; + + break; + case TwoFactorType::phone: + $fields[$option->value] = [ + 'name' => 'phone', + 'label' => __('Phone number'), + 'default' => $this->user->{config('filament-two-factor-auth.phone_number_field', 'phone')}, + 'rules' => ['required'], + ]; + + break; + } + } + + return $fields; } public function confirmAction(): Action @@ -273,8 +356,6 @@ public function confirmTwoFactorAuthentication(ConfirmTwoFactorAuthentication $c public function disableTwoFactorAuthentication(DisableTwoFactorAuthentication $disable): void { - $disable($this->user); - if ($this->user->two_factor_confirmed_at) { Notification::make() ->title(__('Two-Factor Authentication deactivated')) @@ -283,6 +364,7 @@ public function disableTwoFactorAuthentication(DisableTwoFactorAuthentication $d ->duration(5000) ->send(); } + $disable($this->user); $this->showingQrCode = false; $this->showingConfirmation = false;