From 57cd2b718cf58779e2de2b5165f3f95fd62f937b Mon Sep 17 00:00:00 2001 From: Alejandro Romero Herrera Date: Wed, 23 Sep 2020 14:36:32 +0300 Subject: [PATCH 1/4] Remove pesistent sessions across all browsers/devices after pasword change --- .../src/Controller/AccountController.php | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/app/sprinkles/account/src/Controller/AccountController.php b/app/sprinkles/account/src/Controller/AccountController.php index 92b7b3757..6ac8d86e0 100644 --- a/app/sprinkles/account/src/Controller/AccountController.php +++ b/app/sprinkles/account/src/Controller/AccountController.php @@ -1182,13 +1182,19 @@ public function setPassword(Request $request, Response $response, $args) /** @var \UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */ $authenticator = $this->ci->authenticator; - // Log out any existing user, and create a new session - if ($authenticator->check()) { - $authenticator->logout(); + // Remove persistent sessions across all browsers/devices, and create a new session + $user = $passwordReset->user; + + // Make sure logout will have a valid user + if (!$authenticator->check()) { + $authenticator->login($user); } + // Remove persistent sessions + $authenticator->logout(true); + // Auto-login the user (without "remember me") - $user = $passwordReset->user; + // Create a new session $authenticator->login($user); $ms->addMessageTranslated('success', 'WELCOME', $user->export()); @@ -1301,6 +1307,20 @@ public function settings(Request $request, Response $response, $args) $currentUser->save(); + // If user changed his password, remove persistent sessions across all browsers/devices, and create a new session + if (isset($data['password'])) { + + /** @var \UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */ + $authenticator = $this->ci->authenticator; + + // Keep user to re-login after persistent session cleanup + $user = $currentUser; + $authenticator->logout(true); + + // Auto-login the user (without "remember me") + $authenticator->login($user); + } + // Create activity record $this->ci->userActivityLogger->info("User {$currentUser->user_name} updated their account settings.", [ 'type' => 'update_account_settings', From 2b93f307a271e28e5b920648834ebcf7367e0f46 Mon Sep 17 00:00:00 2001 From: Alejandro Romero Herrera Date: Thu, 24 Sep 2020 20:23:57 +0300 Subject: [PATCH 2/4] Show invalid bounty --- app/sprinkles/account/config/default.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/sprinkles/account/config/default.php b/app/sprinkles/account/config/default.php index 741cc4059..5a803042d 100644 --- a/app/sprinkles/account/config/default.php +++ b/app/sprinkles/account/config/default.php @@ -162,7 +162,18 @@ 'check_username_request' => null, 'password_reset_request' => null, 'registration_attempt' => null, - 'sign_in_attempt' => null, + 'sign_in_attempt' => [ + 'method' => 'ip', + 'interval' => 3600, + 'delays' => [ + 4 => 5, + 5 => 10, + 6 => 20, + 7 => 40, + 8 => 80, + 9 => 600, + ], + ], 'verification_request' => null, ], From 6e4a955e48f5de623288b59f3b496593a5cc114d Mon Sep 17 00:00:00 2001 From: Alejandro Romero Herrera Date: Fri, 25 Sep 2020 12:50:47 +0300 Subject: [PATCH 3/4] E-mail verification enabled for e-mail change --- .../account/locale/en_US/messages.php | 1 + .../account/locale/es_ES/messages.php | 1 + .../src/Controller/AccountController.php | 49 +++++++++++++++++-- .../Database/Migrations/v400/UsersTable.php | 1 + .../account/src/Database/Models/User.php | 1 + .../src/Repository/VerificationRepository.php | 12 ++++- .../templates/mail/verify-new-email.html.twig | 21 ++++++++ 7 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 app/sprinkles/account/templates/mail/verify-new-email.html.twig diff --git a/app/sprinkles/account/locale/en_US/messages.php b/app/sprinkles/account/locale/en_US/messages.php index 39637df49..a16bc3588 100644 --- a/app/sprinkles/account/locale/en_US/messages.php +++ b/app/sprinkles/account/locale/en_US/messages.php @@ -39,6 +39,7 @@ '@TRANSLATION' => 'Account settings', 'DESCRIPTION' => 'Update your account settings, including email, name, and password.', 'UPDATED' => 'Account settings updated', + 'NEW_EMAIL' => 'A link to update your email has been sent to {{newEmail}}. Current email {{email}} will be used until you complete this step.', ], 'TOOLS' => 'Account tools', diff --git a/app/sprinkles/account/locale/es_ES/messages.php b/app/sprinkles/account/locale/es_ES/messages.php index ad0a946cb..f96c03f32 100755 --- a/app/sprinkles/account/locale/es_ES/messages.php +++ b/app/sprinkles/account/locale/es_ES/messages.php @@ -39,6 +39,7 @@ '@TRANSLATION' => 'Configuraciones de la cuenta', 'DESCRIPTION' => 'Actualiza la configuración de su cuenta, incluido el correo electrónico, el nombre y la contraseña.', 'UPDATED' => 'Configuración de la cuenta actualizada', + 'NEW_EMAIL' => 'Se ha enviado un enlace para actualizar tu correo electrónico a {{newEmail}}. El correo electrónico actual {{email}} será utilizado hasta que este paso sea completado.', ], 'TOOLS' => 'Herramientas de la cuenta', diff --git a/app/sprinkles/account/src/Controller/AccountController.php b/app/sprinkles/account/src/Controller/AccountController.php index 92b7b3757..d11234ee8 100644 --- a/app/sprinkles/account/src/Controller/AccountController.php +++ b/app/sprinkles/account/src/Controller/AccountController.php @@ -29,6 +29,7 @@ use UserFrosting\Support\Exception\BadRequestException; use UserFrosting\Support\Exception\ForbiddenException; use UserFrosting\Support\Exception\NotFoundException; +use UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface; /** * Controller class for /account/* URLs. Handles account-related activities, including login, registration, password recovery, and account settings. @@ -1277,10 +1278,26 @@ public function settings(Request $request, Response $response, $args) unset($data['passwordcheck']); unset($data['passwordc']); - // If new email was submitted, check that the email address is not in use - if (isset($data['email']) && $data['email'] != $currentUser->email && $classMapper->getClassMapping('user')::findUnique($data['email'], 'email')) { - $ms->addMessageTranslated('danger', 'EMAIL.IN_USE', $data); - $error = true; + // If new email was submitted + if (isset($data['email']) && $data['email'] != $currentUser->email) { + + // Check that the email address is not in use + if ($classMapper->getClassMapping('user')::findUnique($data['email'], 'email')) { + $ms->addMessageTranslated('danger', 'EMAIL.IN_USE', $data); + $error = true; + } elseif ($this->ci->config['site.registration.require_email_verification']) { + // If email verification is configured app/sprinkles/account/src/Account/Registration.php + + // Avoid setting email until verified + $data['newEmail'] = $data['email']; + unset($data['email']); + + // Send verification email + $this->sendVerificationForNewEmail($currentUser, $data['newEmail']); + + // Show verification message + $ms->addMessageTranslated('success', 'ACCOUNT.SETTINGS.NEW_EMAIL', $currentUser->toArray()); + } } if ($error) { @@ -1405,4 +1422,28 @@ public function verify(Request $request, Response $response, $args) // Forward to login page return $response->withRedirect($loginPage); } + + /** + * Send verification email for specified user on new email request. + * + * @param \UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface $user The user to send the email for + * @param String $newEmail The requested new email + */ + protected function sendVerificationForNewEmail(UserInterface $user, String $newEmail) + { + // Try to generate a new verification request + $verification = $this->ci->repoVerification->create($user, $this->ci->config['verification.timeout']); + + // Create and send verification email + $message = new TwigMailMessage($this->ci->view, 'mail/verify-new-email.html.twig'); + + $message->from($this->ci->config['address_book.admin']) + ->addEmailRecipient(new EmailRecipient($newEmail, $user->full_name)) + ->addParams([ + 'user' => $user, + 'token' => $verification->getToken(), + ]); + + $this->ci->mailer->send($message); + } } diff --git a/app/sprinkles/account/src/Database/Migrations/v400/UsersTable.php b/app/sprinkles/account/src/Database/Migrations/v400/UsersTable.php index fa2f841da..66efe08d8 100644 --- a/app/sprinkles/account/src/Database/Migrations/v400/UsersTable.php +++ b/app/sprinkles/account/src/Database/Migrations/v400/UsersTable.php @@ -43,6 +43,7 @@ public function up() $table->boolean('flag_enabled')->default(1)->comment('Set to 1 if the user account is currently enabled, 0 otherwise. Disabled accounts cannot be logged in to, but they retain all of their data and settings.'); $table->integer('last_activity_id')->unsigned()->nullable()->comment('The id of the last activity performed by this user.'); $table->string('password', 255); + $table->string('newEmail', 254)->default(''); $table->softDeletes(); $table->timestamps(); diff --git a/app/sprinkles/account/src/Database/Models/User.php b/app/sprinkles/account/src/Database/Models/User.php index c5eba70eb..fcf1a3bb3 100644 --- a/app/sprinkles/account/src/Database/Models/User.php +++ b/app/sprinkles/account/src/Database/Models/User.php @@ -70,6 +70,7 @@ class User extends Model implements UserInterface 'last_activity_id', 'password', 'deleted_at', + 'newEmail', ]; /** diff --git a/app/sprinkles/account/src/Repository/VerificationRepository.php b/app/sprinkles/account/src/Repository/VerificationRepository.php index c7f76065f..791776655 100644 --- a/app/sprinkles/account/src/Repository/VerificationRepository.php +++ b/app/sprinkles/account/src/Repository/VerificationRepository.php @@ -13,7 +13,7 @@ use UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface; /** - * Token repository class for new account verifications. + * Token repository class for new account verifications and email change verifications. * * @author Alex Weissman (https://alexanderweissman.com) * @@ -31,7 +31,15 @@ class VerificationRepository extends TokenRepository */ protected function updateUser(UserInterface $user, $args) { - $user->flag_verified = 1; + // If this is email update verification + if ( $user->flag_verified && $user->newEmail !== "" ) { + // Update email and remove requested + $user->email = $user->newEmail; + $user->newEmail = ""; + } else { + // New user verification + $user->flag_verified = 1; + } // TODO: generate user activity? or do this in controller? $user->save(); } diff --git a/app/sprinkles/account/templates/mail/verify-new-email.html.twig b/app/sprinkles/account/templates/mail/verify-new-email.html.twig new file mode 100644 index 000000000..532557862 --- /dev/null +++ b/app/sprinkles/account/templates/mail/verify-new-email.html.twig @@ -0,0 +1,21 @@ +{% block subject %} + Hello {{user.first_name}} - please verify your new email for {{site.title}} +{% endblock %} + +{% block body %} +

Dear {{user.first_name}}, +

+

+You are receiving this email because you requested an email change at {{site.title}} ({{site.uri.public}}). +

+

+You will need to verify your new email before change can be applied. Please follow the link below to verify your new email. +

+

+{{site.uri.public}}/account/verify?token={{token}} +

+

+With regards,
+The {{site.title}} Team +

+{% endblock %} From 1e8b5f07ffa308b996e63655c166c46635a0bdae Mon Sep 17 00:00:00 2001 From: Alejandro Romero Herrera Date: Fri, 25 Sep 2020 13:17:46 +0300 Subject: [PATCH 4/4] Removed dev note --- app/sprinkles/account/src/Controller/AccountController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/sprinkles/account/src/Controller/AccountController.php b/app/sprinkles/account/src/Controller/AccountController.php index d11234ee8..ee881621f 100644 --- a/app/sprinkles/account/src/Controller/AccountController.php +++ b/app/sprinkles/account/src/Controller/AccountController.php @@ -1286,7 +1286,7 @@ public function settings(Request $request, Response $response, $args) $ms->addMessageTranslated('danger', 'EMAIL.IN_USE', $data); $error = true; } elseif ($this->ci->config['site.registration.require_email_verification']) { - // If email verification is configured app/sprinkles/account/src/Account/Registration.php + // If email verification is configured // Avoid setting email until verified $data['newEmail'] = $data['email'];