diff --git a/lib/User/Contracts/MailChangeStrategyInterface.php b/lib/User/Contracts/MailChangeStrategyInterface.php new file mode 100644 index 0000000..69fdbf6 --- /dev/null +++ b/lib/User/Contracts/MailChangeStrategyInterface.php @@ -0,0 +1,9 @@ + - */ interface StrategyInterface { - + /** + * @return bool + */ + public function run(); } diff --git a/lib/User/Controller/RecoveryController.php b/lib/User/Controller/RecoveryController.php index fbe893b..14555b8 100644 --- a/lib/User/Controller/RecoveryController.php +++ b/lib/User/Controller/RecoveryController.php @@ -117,7 +117,7 @@ class RecoveryController extends Controller throw new NotFoundHttpException(); } /** @var Token $token */ - $token = $this->tokenQuery->whereIsRecoveryType($id, $code)->one(); + $token = $this->tokenQuery->whereUserId($id)->whereCode($code)->whereIsRecoveryType()->one(); /** @var ResetPasswordEvent $event */ $event = $this->make(ResetPasswordEvent::class, [$token]); diff --git a/lib/User/Controller/RegistrationController.php b/lib/User/Controller/RegistrationController.php index 40a6c6d..045a401 100644 --- a/lib/User/Controller/RegistrationController.php +++ b/lib/User/Controller/RegistrationController.php @@ -11,6 +11,7 @@ use Da\User\Model\SocialNetworkAccount; use Da\User\Model\User; use Da\User\Query\SocialNetworkAccountQuery; use Da\User\Query\UserQuery; +use Da\User\Service\AccountConfirmationService; use Da\User\Service\EmailConfirmationService; use Da\User\Service\ResendConfirmationService; use Da\User\Service\UserConfirmationService; @@ -172,7 +173,7 @@ class RegistrationController extends Controller $this->trigger(UserEvent::EVENT_BEFORE_CONFIRMATION, $event); - if ($this->make(EmailConfirmationService::class, [$code, $user, $userConfirmationService])->run()) { + if ($this->make(AccountConfirmationService::class, [$code, $user, $userConfirmationService])->run()) { Yii::$app->user->login($user, $this->module->rememberLoginLifespan); Yii::$app->session->setFlash('success', Yii::t('user', 'Thank you, registration is now complete.')); diff --git a/lib/User/Controller/SettingsController.php b/lib/User/Controller/SettingsController.php new file mode 100644 index 0000000..4d57d45 --- /dev/null +++ b/lib/User/Controller/SettingsController.php @@ -0,0 +1,160 @@ +profileQuery = $profileQuery; + $this->userQuery = $userQuery; + $this->socialNetworkAccountQuery = $socialNetworkAccountQuery; + parent::__construct($id, $module, $config); + } + + /** + * @inheritdoc + */ + public function behaviors() + { + return [ + 'verbs' => [ + 'class' => VerbFilter::className(), + 'actions' => [ + 'disconnect' => ['post'], + 'delete' => ['post'], + ], + ], + 'access' => [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'allow' => true, + 'actions' => ['profile', 'account', 'networks', 'disconnect', 'delete'], + 'roles' => ['@'], + ], + [ + 'allow' => true, + 'actions' => ['confirm'], + 'roles' => ['?', '@'], + ], + ], + ], + ]; + } + + public function actionProfile() + { + $profile = $this->profileQuery->whereId(Yii::$app->user->identity->getId())->one(); + + if ($profile === null) { + $profile = $this->make(Profile::class); + $profile->link('user', Yii::$app->user->identity); + } + + $event = $this->make(ProfileEvent::class, [$profile]); + + $this->make(AjaxRequestModelValidator::class, [$profile])->validate(); + + if ($profile->load(Yii::$app->request->post())) { + $this->trigger(UserEvent::EVENT_BEFORE_PROFILE_UPDATE, $event); + if ($profile->save()) { + Yii::$app->getSession()->setFlash('success', Yii::t('user', 'Your profile has been updated')); + $this->trigger(UserEvent::EVENT_AFTER_PROFILE_UPDATE, $event); + + return $this->refresh(); + } + } + + return $this->render( + 'profile', + [ + 'model' => $profile, + ] + ); + } + + public function actionAccount() + { + $form = $this->make(SettingsForm::class); + $event = $this->make(FormEvent::class, [$form]); + + $this->make(AjaxRequestModelValidator::class, [$form])->validate(); + + if($form->load(Yii::$app->request->post())) { + $this->trigger(UserEvent::EVENT_BEFORE_ACCOUNT_UPDATE, $event); + if($form->save()) { + Yii::$app->getSession()->setFlash('success', Yii::t('user', 'Your account details have been updated-')); + $this->trigger(UserEvent::EVENT_AFTER_ACCOUNT_UPDATE, $event); + + return $this->refresh(); + } + } + + return $this->render('account', [ + 'model' => $form, + ]); + } + + public function actionConfirm($id, $code) { + $user = $this->userQuery->whereId($id)->one(); + + if ($user === null || $this->module->emailChangeStrategy == MailChangeStrategyInterface::TYPE_INSECURE) { + throw new NotFoundHttpException(); + } + $event = $this->make(UserEvent::class, [$user]); + + $this->trigger(UserEvent::EVENT_BEFORE_CONFIRMATION, $event); + if($this->make(EmailChangeService::class, [$code, $user])->run()) { + $this->trigger(UserEvent::EVENT_AFTER_CONFIRMATION, $event); + } + + return $this->redirect(['account']); + } +} diff --git a/lib/User/Event/UserEvent.php b/lib/User/Event/UserEvent.php index 5767841..73a5e8c 100644 --- a/lib/User/Event/UserEvent.php +++ b/lib/User/Event/UserEvent.php @@ -10,6 +10,8 @@ class UserEvent extends Event const EVENT_AFTER_CREATE = 'afterCreate'; const EVENT_BEFORE_REGISTER = 'beforeRegister'; const EVENT_AFTER_REGISTER = 'afterRegister'; + const EVENT_BEFORE_ACCOUNT_UPDATE = 'beforeAccountUpdate'; + const EVENT_AFTER_ACCOUNT_UPDATE = 'afterAccountUpdate'; const EVENT_BEFORE_PROFILE_UPDATE = 'beforeProfileUpdate'; const EVENT_AFTER_PROFILE_UPDATE = 'afterProfileUpdate'; const EVENT_BEFORE_CONFIRMATION = 'beforeConfirmation'; diff --git a/lib/User/Factory/EmailChangeStrategyFactory.php b/lib/User/Factory/EmailChangeStrategyFactory.php new file mode 100644 index 0000000..53fd39d --- /dev/null +++ b/lib/User/Factory/EmailChangeStrategyFactory.php @@ -0,0 +1,67 @@ + InsecureEmailChangeStrategy::class, + MailChangeStrategyInterface::TYPE_DEFAULT => DefaultEmailChangeStrategy::class, + MailChangeStrategyInterface::TYPE_SECURE => SecureEmailChangeStrategy::class + ]; + + /** + * @param $strategy + * @param SettingsForm $form + * + * @return MailChangeStrategyInterface + * @throws Exception + */ + public static function makeByStrategyType($strategy, SettingsForm $form) + { + if (array_key_exists($strategy, static::$map)) { + return Yii::createObject(static::$map[$strategy], [$form]); + } + + throw new Exception('Unknown strategy type'); + } + + /** + * @param SettingsForm $form + * + * @return DefaultEmailChangeStrategy + */ + public static function makeDefaultEmailChangeStrategy(SettingsForm $form) + { + return Yii::createObject(static::$map[MailChangeStrategyInterface::TYPE_DEFAULT], [$form]); + } + + /** + * @param SettingsForm $form + * + * @return InsecureEmailChangeStrategy + */ + public static function makeInsecureEmailChangeStrategy(SettingsForm $form) + { + return Yii::createObject(static::$map[MailChangeStrategyInterface::TYPE_INSECURE], [$form]); + } + + /** + * @param SettingsForm $form + * + * @return SecureEmailChangeStrategy + */ + public static function makeSecureEmailChangeStrategy(SettingsForm $form) + { + return Yii::createObject(static::$map[MailChangeStrategyInterface::TYPE_SECURE], [$form]); + } +} diff --git a/lib/User/Factory/MailFactory.php b/lib/User/Factory/MailFactory.php index cbf7227..63d61a9 100644 --- a/lib/User/Factory/MailFactory.php +++ b/lib/User/Factory/MailFactory.php @@ -52,6 +52,12 @@ class MailFactory return static::makeMailerService($from, $to, $subject, 'recovery', $params); } + /** + * @param User $user + * @param Token|null $token + * + * @return MailService + */ public static function makeConfirmationMailerService(User $user, Token $token = null) { /** @var Module $module */ @@ -67,6 +73,29 @@ class MailFactory return static::makeMailerService($from, $to, $subject, 'recovery', $params); } + /** + * @param User $user + * @param Token $token + * + * @return MailService + */ + public static function makeReconfirmationMailerService(User $user, Token $token) { + /** @var Module $module */ + $module = Yii::$app->getModule('user'); + $to = $token->type === Token::TYPE_CONFIRM_NEW_EMAIL + ? $user->unconfirmed_email + : $user->email; + + $from = $module->mailParams['fromEmail']; + $subject = $module->mailParams['reconfirmationMailSubject']; + $params = [ + 'user' => $token && $token->user ? $token->user : null, + 'token' => $token + ]; + + return static::makeMailerService($from, $to, $subject, 'recovery', $params); + } + /** * Builds a MailerService * diff --git a/lib/User/Factory/TokenFactory.php b/lib/User/Factory/TokenFactory.php index 8d3255d..07c5051 100644 --- a/lib/User/Factory/TokenFactory.php +++ b/lib/User/Factory/TokenFactory.php @@ -37,6 +37,20 @@ class TokenFactory return $token; } + /** + * @param $userId + * + * @return Token + */ + public static function makeConfirmOldMailToken($userId) + { + $token = self::make($userId, Token::TYPE_CONFIRM_OLD_EMAIL); + + $token->save(false); + + return $token; + } + /** * @param $userId * diff --git a/lib/User/Form/SettingsForm.php b/lib/User/Form/SettingsForm.php index 9620def..ac0524c 100644 --- a/lib/User/Form/SettingsForm.php +++ b/lib/User/Form/SettingsForm.php @@ -1,113 +1,107 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace dektrium\user\models; +namespace Da\User\Form; use Da\User\Factory\TokenFactory; -use dektrium\user\helpers\Password; -use dektrium\user\Mailer; -use dektrium\user\Module; -use dektrium\user\traits\ModuleTrait; +use Da\User\Helper\SecurityHelper; +use Da\User\Model\User; +use Da\User\Traits\ContainerTrait; +use Da\User\Traits\ModuleTrait; use Yii; use yii\base\Model; -/** - * SettingsForm gets user's username, email and password and changes them. - * - * @property User $user - * - * @author Dmitry Erofeev - */ class SettingsForm extends Model { use ModuleTrait; + use ContainerTrait; - /** @var string */ + /** + * @var string + */ public $email; - - /** @var string */ + /** + * @var string + */ public $username; - - /** @var string */ + /** + * @var string + */ public $new_password; - - /** @var string */ + /** + * @var string + */ public $current_password; - - /** @var Mailer */ - protected $mailer; + /** + * @var SecurityHelper + */ + protected $securityHelper; /** @var User */ - private $_user; + private $user; - /** @return User */ - public function getUser() + public function __construct(SecurityHelper $securityHelper, array $config) { - if ($this->_user == null) { - $this->_user = Yii::$app->user->identity; - } - - return $this->_user; - } - - /** @inheritdoc */ - public function __construct(Mailer $mailer, $config = []) - { - $this->mailer = $mailer; - $this->setAttributes([ - 'username' => $this->user->username, - 'email' => $this->user->unconfirmed_email ?: $this->user->email, - ], false); + $this->securityHelper = $securityHelper; parent::__construct($config); } - /** @inheritdoc */ + /** + * @return array + */ public function rules() { return [ 'usernameRequired' => ['username', 'required'], 'usernameTrim' => ['username', 'filter', 'filter' => 'trim'], - 'usernameLength' => ['username', 'string', 'min' => 3, 'max' => 255], + 'usernameLength' => ['username', 'string', 'min' => 3, 'max' => 255], 'usernamePattern' => ['username', 'match', 'pattern' => '/^[-a-zA-Z0-9_\.@]+$/'], 'emailRequired' => ['email', 'required'], 'emailTrim' => ['email', 'filter', 'filter' => 'trim'], 'emailPattern' => ['email', 'email'], - 'emailUsernameUnique' => [['email', 'username'], 'unique', 'when' => function ($model, $attribute) { - return $this->user->$attribute != $model->$attribute; - }, 'targetClass' => $this->module->modelMap['User']], + 'emailUsernameUnique' => [ + ['email', 'username'], + 'unique', + 'when' => function ($model, $attribute) { + return $this->user->$attribute != $model->$attribute; + }, + 'targetClass' => $this->getClassMap()[User::class] + ], 'newPasswordLength' => ['new_password', 'string', 'max' => 72, 'min' => 6], 'currentPasswordRequired' => ['current_password', 'required'], - 'currentPasswordValidate' => ['current_password', function ($attr) { - if (!Password::validate($this->$attr, $this->user->password_hash)) { - $this->addError($attr, Yii::t('user', 'Current password is not valid')); + 'currentPasswordValidate' => [ + 'current_password', + function ($attribute) { + if (!$this->securityHelper->validatePassword($this->$attribute, $this->user->password_hash)) { + $this->addError($attribute, Yii::t('user', 'Current password is not valid')); + } } - }], + ], ]; } - /** @inheritdoc */ + /** + * @inheritdoc + */ public function attributeLabels() { return [ - 'email' => Yii::t('user', 'Email'), - 'username' => Yii::t('user', 'Username'), - 'new_password' => Yii::t('user', 'New password'), + 'email' => Yii::t('user', 'Email'), + 'username' => Yii::t('user', 'Username'), + 'new_password' => Yii::t('user', 'New password'), 'current_password' => Yii::t('user', 'Current password'), ]; } - /** @inheritdoc */ - public function formName() + /** + * @return User|null|\yii\web\IdentityInterface + */ + public function getUser() { - return 'settings-form'; + if ($this->user == null) { + $this->user = Yii::$app->user->identity; + } + + return $this->user; } /** @@ -179,11 +173,13 @@ class SettingsForm extends Model { $this->defaultEmailChange(); /** @var Token $token */ - $token = Yii::createObject([ - 'class' => Token::className(), - 'user_id' => $this->user->id, - 'type' => Token::TYPE_CONFIRM_OLD_EMAIL, - ]); + $token = Yii::createObject( + [ + 'class' => Token::className(), + 'user_id' => $this->user->id, + 'type' => Token::TYPE_CONFIRM_OLD_EMAIL, + ] + ); $token->save(false); $this->mailer->sendReconfirmationMessage($this->user, $token); diff --git a/lib/User/Model/User.php b/lib/User/Model/User.php index 3a46bb4..ae8d1bf 100644 --- a/lib/User/Model/User.php +++ b/lib/User/Model/User.php @@ -24,11 +24,13 @@ use yii\web\IdentityInterface; * @property integer $id * @property string $username * @property string $email + * @property string $unconfirmed_email * @property string $password_hash * @property string $auth_key * @property integer $registration_ip * @property integer $confirmed_at * @property integer $blocked_at + * @property integer $flags * @property integer $created_at * @property integer $updated_at * @@ -41,6 +43,10 @@ class User extends ActiveRecord implements IdentityInterface use ModuleTrait; use ContainerTrait; + // following constants are used on secured email changing process + const OLD_EMAIL_CONFIRMED = 0b1; + const NEW_EMAIL_CONFIRMED = 0b10; + /** * @var string default user name regular expression. */ diff --git a/lib/User/Module.php b/lib/User/Module.php index a07788a..df7fc9a 100644 --- a/lib/User/Module.php +++ b/lib/User/Module.php @@ -2,7 +2,7 @@ namespace Da\User; -use Da\User\Strategy\DefaultEmailChangeStrategy; +use Da\User\Contracts\MailChangeStrategyInterface; class Module extends \yii\base\Module { @@ -29,7 +29,7 @@ class Module extends \yii\base\Module /** * @var string the class name of the strategy class to handle user's email change. */ - public $emailChangeStrategy = DefaultEmailChangeStrategy::class; + public $emailChangeStrategy = MailChangeStrategyInterface::TYPE_DEFAULT; /** * @var int the time user will be auto logged in. */ diff --git a/lib/User/Query/TokenQuery.php b/lib/User/Query/TokenQuery.php index 19e970c..a12a7c1 100644 --- a/lib/User/Query/TokenQuery.php +++ b/lib/User/Query/TokenQuery.php @@ -6,13 +6,49 @@ use yii\db\ActiveQuery; class TokenQuery extends ActiveQuery { - public function whereIsRecoveryType($userId, $code) + /** + * @param $userId + * + * @return $this + */ + public function whereUserId($userId) { - return $this->andWhere(['user_id' => $userId, 'code' => $code, 'type' => Token::TYPE_RECOVERY]); + return $this->andWhere(['user_id' => $userId]); } - public function whereIsConfirmationType($userId, $code) + /** + * @param $code + * + * @return $this + */ + public function whereCode($code) { - return $this->andWhere(['user_id' => $userId, 'code' => $code, 'type' => Token::TYPE_CONFIRM_NEW_EMAIL]); + return $this->andWhere(['code' => $code]); + } + + /** + * @return $this + */ + public function whereIsRecoveryType() + { + return $this->andWhere(['type' => Token::TYPE_RECOVERY]); + } + + /** + * @return $this + */ + public function whereIsConfirmationType() + { + return $this->andWhere(['type' => Token::TYPE_CONFIRM_NEW_EMAIL]); + } + + /** + * @param array $types + * + * @return $this + */ + public function whereIsTypes(array $types) + { + return $this->andWhere(['in', 'type', $types]); } } diff --git a/lib/User/Query/UserQuery.php b/lib/User/Query/UserQuery.php index 4ebe09f..17f53f5 100644 --- a/lib/User/Query/UserQuery.php +++ b/lib/User/Query/UserQuery.php @@ -6,6 +6,11 @@ use yii\db\ActiveQuery; class UserQuery extends ActiveQuery { + /** + * @param $usernameOrEmail + * + * @return $this + */ public function whereUsernameOrEmail($usernameOrEmail) { return filter_var($usernameOrEmail, FILTER_VALIDATE_EMAIL) @@ -13,18 +18,44 @@ class UserQuery extends ActiveQuery : $this->whereUsername($usernameOrEmail); } + /** + * @param $email + * + * @return $this + */ public function whereEmail($email) { return $this->andWhere(['email' => $email]); } + /** + * @param $username + * + * @return $this + */ public function whereUsername($username) { return $this->andWhere(['username' => $username]); } + /** + * @param $id + * + * @return $this + */ public function whereId($id) { return $this->andWhere(['id' => $id]); } + + + /** + * @param $id + * + * @return $this + */ + public function whereNotId($id) + { + return $this->andWhere(['<>', 'id', $id]); + } } diff --git a/lib/User/Service/EmailConfirmationService.php b/lib/User/Service/AccountConfirmationService.php similarity index 78% rename from lib/User/Service/EmailConfirmationService.php rename to lib/User/Service/AccountConfirmationService.php index 8a071fc..f05338e 100644 --- a/lib/User/Service/EmailConfirmationService.php +++ b/lib/User/Service/AccountConfirmationService.php @@ -8,7 +8,7 @@ use Da\User\Model\Token; use Da\User\Model\User; use Da\User\Query\TokenQuery; -class EmailConfirmationService implements ServiceInterface +class AccountConfirmationService implements ServiceInterface { protected $model; protected $code; @@ -29,7 +29,11 @@ class EmailConfirmationService implements ServiceInterface public function run() { - $token = $this->tokenQuery->whereIsConfirmationType($this->model->id, $this->code)->one(); + $token = $this->tokenQuery + ->whereUserId($this->model->id) + ->whereCode($this->code) + ->whereIsConfirmationType() + ->one(); if ($token instanceof Token && !$token->getIsExpired()) { $token->delete(); diff --git a/lib/User/Service/EmailChangeService.php b/lib/User/Service/EmailChangeService.php new file mode 100644 index 0000000..6b96dbd --- /dev/null +++ b/lib/User/Service/EmailChangeService.php @@ -0,0 +1,87 @@ +code = $code; + $this->model = $model; + $this->tokenQuery = $tokenQuery; + $this->userQuery = $userQuery; + } + + public function run() + { + /** @var Token $token */ + $token = $this->tokenQuery + ->whereUserId($this->model->id) + ->whereCode($this->code) + ->whereIsTypes([Token::TYPE_CONFIRM_NEW_EMAIL, Token::TYPE_CONFIRM_OLD_EMAIL]) + ->one(); + + if ($token === null || $token->getIsExpired()) { + Yii::$app->session->setFlash('danger', Yii::t('user', 'Your confirmation token is invalid or expired')); + + return false; + } else { + $token->delete(); + if (empty($this->model->unconfirmed_email)) { + Yii::$app->session->setFlash('danger', Yii::t('user', 'An error occurred processing your request')); + + } elseif ($this->userQuery->whereEmail($this->model->unconfirmed_email)->exists() === false) { + + if ($this->getModule()->emailChangeStrategy === MailChangeStrategyInterface::TYPE_SECURE) { + if ($token->type === Token::TYPE_CONFIRM_NEW_EMAIL) { + $this->model->flags |= User::NEW_EMAIL_CONFIRMED; + Yii::$app->session->setFlash( + 'success', + Yii::t( + 'user', + 'Awesome, almost there. ' . + 'Now you need to click the confirmation link sent to your old email address.' + ) + ); + } elseif ($token->type === Token::TYPE_CONFIRM_OLD_EMAIL) { + $this->model->flags |= User::OLD_EMAIL_CONFIRMED; + Yii::$app->session->setFlash( + 'success', + Yii::t( + 'user', + 'Awesome, almost there. ' . + 'Now you need to click the confirmation link sent to your new email address.' + ) + ); + } + } + if ($this->getModule()->emailChangeStrategy === MailChangeStrategyInterface::TYPE_DEFAULT + || ($this->model->flags & User::NEW_EMAIL_CONFIRMED & $this->model->flags & User::OLD_EMAIL_CONFIRMED) + ) { + $this->model->email = $this->model->unconfirmed_email; + $this->model->unconfirmed_email = null; + Yii::$app->session->setFlash('success', Yii::t('user', 'Your email address has been changed')); + } + + return $this->model->save(false); + } + } + return false; + } + +} diff --git a/lib/User/Strategy/DefaultEmailChangeStrategy.php b/lib/User/Strategy/DefaultEmailChangeStrategy.php index 1f8b91a..6f45831 100644 --- a/lib/User/Strategy/DefaultEmailChangeStrategy.php +++ b/lib/User/Strategy/DefaultEmailChangeStrategy.php @@ -1,17 +1,43 @@ - */ -class DefaultEmailChangeStrategy implements StrategyInterface +class DefaultEmailChangeStrategy implements MailChangeStrategyInterface { + use ContainerTrait; + + protected $form; + + public function __construct(SettingsForm $form) + { + $this->form = $form; + + } + + public function run() + { + $this->form->getUser()->unconfirmed_email = $this->form->email; + + $token = TokenFactory::makeConfirmNewMailToken($this->form->getUser()->id); + + $mailService = MailFactory::makeReconfirmationMailerService($this->form->getUser(), $token); + + if ($mailService->run()) { + Yii::$app + ->session + ->setFlash('info', Yii::t('user', 'A confirmation message has been sent to your new email address')); + + return true; + } + + return false; + + } } diff --git a/lib/User/Strategy/InsecureEmailChangeStrategy.php b/lib/User/Strategy/InsecureEmailChangeStrategy.php index 77c05c3..546496f 100644 --- a/lib/User/Strategy/InsecureEmailChangeStrategy.php +++ b/lib/User/Strategy/InsecureEmailChangeStrategy.php @@ -1,17 +1,23 @@ - */ -class InsecureEmailChangeStrategy implements StrategyInterface + +class InsecureEmailChangeStrategy implements MailChangeStrategyInterface { + protected $form; + + public function __construct(SettingsForm $form) + { + $this->form = $form; + } + + public function run() + { + $this->form->getUser()->email = $this->form->email; + return $this->form->getUser()->save(); + } } diff --git a/lib/User/Strategy/SecureEmailChangeStrategy.php b/lib/User/Strategy/SecureEmailChangeStrategy.php index 41b46fc..df8d605 100644 --- a/lib/User/Strategy/SecureEmailChangeStrategy.php +++ b/lib/User/Strategy/SecureEmailChangeStrategy.php @@ -1,17 +1,55 @@ - */ -class SecureEmailChangeStrategy implements StrategyInterface +class SecureEmailChangeStrategy implements MailChangeStrategyInterface { + use ContainerTrait; + + protected $form; + + public function __construct(SettingsForm $form) + { + $this->form = $form; + + } + + public function run() + { + if ($this->make(DefaultEmailChangeStrategy::class, [$this->form])->run()) { + + $token = TokenFactory::makeConfirmOldMailToken($this->form->getUser()->id); + $mailService = MailFactory::makeReconfirmationMailerService($this->form->getUser(), $token); + + if ($mailService->run()) { + // unset flags if they exist + $this->form->getUser()->flags &= ~User::NEW_EMAIL_CONFIRMED; + $this->form->getUser()->flags &= ~User::OLD_EMAIL_CONFIRMED; + if ($this->form->getUser()->save(false)) { + Yii::$app + ->session + ->setFlash( + 'info', + Yii::t( + 'user', + 'We have sent confirmation links to both old and new email addresses. ' . + 'You must click both links to complete your request.' + ) + ); + + return true; + } + } + } + + return false; + } }