From b9ee667829d1b1e9d51a26fff01e62790e3ea86f Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sun, 13 Mar 2016 02:19:00 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B2=D0=B8=D1=87=D0=BD?= =?UTF-8?q?=D0=B0=D1=8F=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20=D1=84=D0=BE=D1=80=D0=BC=D1=8B=20=D0=BE=D1=82?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20=D0=BD=D0=BE=D0=B2=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=20=D0=BF=D0=B8=D1=81=D1=8C=D0=BC=D0=B0=20=D1=81=20?= =?UTF-8?q?=D0=B0=D0=BA=D1=82=D0=B8=D0=B2=D0=B0=D1=86=D0=B8=D0=B5=D0=B9=20?= =?UTF-8?q?=D0=B0=D0=BA=D0=BA=D0=B0=D1=83=D0=BD=D1=82=D0=B0,=20=D1=87?= =?UTF-8?q?=D1=83=D1=82=D1=8C-=D1=87=D1=83=D1=82=D1=8C=20=D1=80=D0=B5?= =?UTF-8?q?=D1=84=D0=B0=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=D0=B0=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/controllers/SignupController.php | 31 +++++- api/models/BaseKeyConfirmationForm.php | 1 + api/models/NewAccountActivationForm.php | 103 ++++++++++++++++++ api/models/RegistrationForm.php | 40 +++---- common/models/Account.php | 8 +- .../api/_pages/EmailConfirmRoute.php | 19 ---- .../codeception/api/_pages/RegisterRoute.php | 17 --- tests/codeception/api/_pages/SignupRoute.php | 28 +++++ .../api/functional/EmailConfirmationCest.php | 6 +- .../functional/NewAccountActivationCest.php | 21 ++++ .../api/functional/RegisterCest.php | 34 +++--- 11 files changed, 227 insertions(+), 81 deletions(-) create mode 100644 api/models/NewAccountActivationForm.php delete mode 100644 tests/codeception/api/_pages/EmailConfirmRoute.php delete mode 100644 tests/codeception/api/_pages/RegisterRoute.php create mode 100644 tests/codeception/api/_pages/SignupRoute.php create mode 100644 tests/codeception/api/functional/NewAccountActivationCest.php diff --git a/api/controllers/SignupController.php b/api/controllers/SignupController.php index db0853d..dcb73b9 100644 --- a/api/controllers/SignupController.php +++ b/api/controllers/SignupController.php @@ -2,6 +2,7 @@ namespace api\controllers; use api\models\ConfirmEmailForm; +use api\models\NewAccountActivationForm; use api\models\RegistrationForm; use Yii; use yii\filters\AccessControl; @@ -12,13 +13,13 @@ class SignupController extends Controller { public function behaviors() { return ArrayHelper::merge(parent::behaviors(), [ 'authenticator' => [ - 'except' => ['index', 'confirm'], + 'except' => ['index', 'new-message', 'confirm'], ], 'access' => [ 'class' => AccessControl::class, 'rules' => [ [ - 'actions' => ['index', 'confirm'], + 'actions' => ['index', 'new-message', 'confirm'], 'allow' => true, 'roles' => ['?'], ], @@ -31,6 +32,7 @@ class SignupController extends Controller { return [ 'register' => ['POST'], 'confirm' => ['POST'], + 'new-message' => ['POST'], ]; } @@ -49,6 +51,31 @@ class SignupController extends Controller { ]; } + public function actionNewMessage() { + $model = new NewAccountActivationForm(); + $model->load(Yii::$app->request->post()); + if (!$model->sendNewMessage()) { + $response = [ + 'success' => false, + 'errors' => $this->normalizeModelErrors($model->getErrors()), + ]; + + if ($response['errors']['email'] === 'error.recently_sent_message') { + $activeActivation = $model->getActiveActivation(); + $response['data'] = [ + 'can_repeat_in' => $activeActivation->created_at - time() + NewAccountActivationForm::REPEAT_FREQUENCY, + 'repeat_frequency' => NewAccountActivationForm::REPEAT_FREQUENCY, + ]; + } + + return $response; + } + + return [ + 'success' => true, + ]; + } + public function actionConfirm() { $model = new ConfirmEmailForm(); $model->load(Yii::$app->request->post()); diff --git a/api/models/BaseKeyConfirmationForm.php b/api/models/BaseKeyConfirmationForm.php index 7ebac48..fd0b898 100644 --- a/api/models/BaseKeyConfirmationForm.php +++ b/api/models/BaseKeyConfirmationForm.php @@ -11,6 +11,7 @@ class BaseKeyConfirmationForm extends BaseApiForm { public function rules() { return [ + // TODO: нужно провалидировать количество попыток ввода кода для определённого IP адреса и в случае чего запросить капчу ['key', 'required', 'message' => 'error.key_is_required'], ['key', 'validateKey'], ]; diff --git a/api/models/NewAccountActivationForm.php b/api/models/NewAccountActivationForm.php new file mode 100644 index 0000000..ac24330 --- /dev/null +++ b/api/models/NewAccountActivationForm.php @@ -0,0 +1,103 @@ + 'trim'], + ['email', 'required', 'message' => 'error.email_required'], + ['email', 'validateAccountForEmail'], + ['email', 'validateExistsActivation'], + ]; + } + + public function validateAccountForEmail($attribute) { + if (!$this->hasErrors($attribute)) { + $account = $this->getAccount(); + if ($account && $account->status === Account::STATUS_ACTIVE) { + $this->addError($attribute, "error.account_already_activated"); + } elseif (!$account) { + $this->addError($attribute, "error.{$attribute}_not_found"); + } + } + } + + public function validateExistsActivation($attribute) { + if (!$this->hasErrors($attribute)) { + if ($this->getActiveActivation() !== null) { + $this->addError($attribute, 'error.recently_sent_message'); + } + } + } + + public function sendNewMessage() { + if (!$this->validate()) { + return false; + } + + $account = $this->getAccount(); + $transaction = Yii::$app->db->beginTransaction(); + try { + // Удаляем все активации аккаунта для пользователя этого E-mail адреса + /** @var EmailActivation[] $activations */ + $activations = $account->getEmailActivations() + ->andWhere(['type' => EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION]) + ->all(); + + foreach ($activations as $activation) { + $activation->delete(); + } + + $activation = new EmailActivation(); + $activation->account_id = $account->id; + $activation->type = EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION; + $activation->key = UserFriendlyRandomKey::make(); + if (!$activation->save()) { + throw new ErrorException('Unable save email-activation model.'); + } + + $regForm = new RegistrationForm(); + $regForm->sendMail($activation, $account); + + $transaction->commit(); + } catch (ErrorException $e) { + $transaction->rollBack(); + throw $e; + } + + return true; + } + + /** + * @return Account|null + */ + public function getAccount() { + return Account::find() + ->andWhere(['email' => $this->email]) + ->one(); + } + + /** + * @return EmailActivation|null + */ + public function getActiveActivation() { + return $this->getAccount() + ->getEmailActivations() + ->andWhere(['type' => EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION]) + ->andWhere(['>=', 'created_at', time() - self::REPEAT_FREQUENCY]) + ->one(); + } + +} diff --git a/api/models/RegistrationForm.php b/api/models/RegistrationForm.php index f30f722..bde9c25 100644 --- a/api/models/RegistrationForm.php +++ b/api/models/RegistrationForm.php @@ -82,25 +82,7 @@ class RegistrationForm extends BaseApiForm { throw new ErrorException('Unable save email-activation model.'); } - /** @var \yii\swiftmailer\Mailer $mailer */ - $mailer = Yii::$app->mailer; - /** @var \yii\swiftmailer\Message $message */ - $message = $mailer->compose( - [ - 'html' => '@app/mails/registration-confirmation-html', - 'text' => '@app/mails/registration-confirmation-text', - ], - [ - 'key' => $emailActivation->key, - ] - ) - ->setTo([$account->email => $account->username]) - ->setFrom([Yii::$app->params['fromEmail'] => 'Ely.by Accounts']) - ->setSubject('Ely.by Account registration'); - - if (!$message->send()) { - throw new ErrorException('Unable send email with activation code.'); - } + $this->sendMail($emailActivation, $account); $transaction->commit(); } catch (ErrorException $e) { @@ -111,4 +93,24 @@ class RegistrationForm extends BaseApiForm { return $account; } + // TODO: подумать, чтобы вынести этот метод в какую-то отдельную конструкцию, т.к. используется и внутри NewAccountActivationForm + public function sendMail(EmailActivation $emailActivation, Account $account) { + /** @var \yii\swiftmailer\Mailer $mailer */ + $mailer = Yii::$app->mailer; + /** @var \yii\swiftmailer\Message $message */ + $message = $mailer->compose([ + 'html' => '@app/mails/registration-confirmation-html', + 'text' => '@app/mails/registration-confirmation-text', + ], [ + 'key' => $emailActivation->key, + ]) + ->setTo([$account->email => $account->username]) + ->setFrom([Yii::$app->params['fromEmail'] => 'Ely.by Accounts']) + ->setSubject('Ely.by Account registration'); + + if (!$message->send()) { + throw new ErrorException('Unable send email with activation code.'); + } + } + } diff --git a/common/models/Account.php b/common/models/Account.php index b961a50..f9f0086 100644 --- a/common/models/Account.php +++ b/common/models/Account.php @@ -189,7 +189,7 @@ class Account extends ActiveRecord implements IdentityInterface { } public function getEmailActivations() { - return $this->hasMany(EmailActivation::class, ['id' => 'account_id']); + return $this->hasMany(EmailActivation::class, ['account_id' => 'id']); } public function getSessions() { @@ -206,9 +206,9 @@ class Account extends ActiveRecord implements IdentityInterface { * @return bool */ public function canAutoApprove(OauthClient $client, array $scopes = []) { - //if ($client->is_trusted) { - // return true; - //} + if ($client->is_trusted) { + return true; + } /** @var OauthSession|null $session */ $session = $this->getSessions()->andWhere(['client_id' => $client->id])->one(); diff --git a/tests/codeception/api/_pages/EmailConfirmRoute.php b/tests/codeception/api/_pages/EmailConfirmRoute.php deleted file mode 100644 index c664907..0000000 --- a/tests/codeception/api/_pages/EmailConfirmRoute.php +++ /dev/null @@ -1,19 +0,0 @@ -actor->sendPOST($this->getUrl(), [ - 'key' => $key, - ]); - } - -} diff --git a/tests/codeception/api/_pages/RegisterRoute.php b/tests/codeception/api/_pages/RegisterRoute.php deleted file mode 100644 index 89d166c..0000000 --- a/tests/codeception/api/_pages/RegisterRoute.php +++ /dev/null @@ -1,17 +0,0 @@ -actor->sendPOST($this->getUrl(), $registrationData); - } - -} diff --git a/tests/codeception/api/_pages/SignupRoute.php b/tests/codeception/api/_pages/SignupRoute.php new file mode 100644 index 0000000..6f796bd --- /dev/null +++ b/tests/codeception/api/_pages/SignupRoute.php @@ -0,0 +1,28 @@ +route = ['signup/index']; + $this->actor->sendPOST($this->getUrl(), $registrationData); + } + + public function sendNewMessage($email = '') { + $this->route = ['signup/new-message']; + $this->actor->sendPOST($this->getUrl(), ['email' => $email]); + } + + public function confirm($key = '') { + $this->route = ['signup/confirm']; + $this->actor->sendPOST($this->getUrl(), [ + 'key' => $key, + ]); + } + +} diff --git a/tests/codeception/api/functional/EmailConfirmationCest.php b/tests/codeception/api/functional/EmailConfirmationCest.php index c3d563d..0223d44 100644 --- a/tests/codeception/api/functional/EmailConfirmationCest.php +++ b/tests/codeception/api/functional/EmailConfirmationCest.php @@ -1,12 +1,12 @@ wantTo('see error.key_is_required expected if key is not set'); $route->confirm(); @@ -28,7 +28,7 @@ class EmailConfirmationCest { } public function testLoginByEmailCorrect(FunctionalTester $I) { - $route = new EmailConfirmRoute($I); + $route = new SignupRoute($I); $I->wantTo('confirm my email using correct activation key'); $route->confirm('HABGCABHJ1234HBHVD'); diff --git a/tests/codeception/api/functional/NewAccountActivationCest.php b/tests/codeception/api/functional/NewAccountActivationCest.php new file mode 100644 index 0000000..a4291ce --- /dev/null +++ b/tests/codeception/api/functional/NewAccountActivationCest.php @@ -0,0 +1,21 @@ +wantTo('ensure that signup works'); + $route->sendNewMessage('achristiansen@gmail.com'); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson(['success' => true]); + $I->cantSeeResponseJsonMatchesJsonPath('$.errors'); + } + +} diff --git a/tests/codeception/api/functional/RegisterCest.php b/tests/codeception/api/functional/RegisterCest.php index 8526afa..58c48bd 100644 --- a/tests/codeception/api/functional/RegisterCest.php +++ b/tests/codeception/api/functional/RegisterCest.php @@ -3,7 +3,7 @@ namespace tests\codeception\api\functional; use Codeception\Specify; use common\models\Account; -use tests\codeception\api\_pages\RegisterRoute; +use tests\codeception\api\_pages\SignupRoute; use tests\codeception\api\FunctionalTester; class RegisterCest { @@ -16,10 +16,10 @@ class RegisterCest { } public function testIncorrectRegistration(FunctionalTester $I) { - $route = new RegisterRoute($I); + $route = new SignupRoute($I); $I->wantTo('get error.you_must_accept_rules if we don\'t accept rules'); - $route->send([ + $route->register([ 'username' => 'ErickSkrauch', 'email' => 'erickskrauch@ely.by', 'password' => 'some_password', @@ -33,7 +33,7 @@ class RegisterCest { ]); $I->wantTo('don\'t see error.you_must_accept_rules if we accept rules'); - $route->send([ + $route->register([ 'rulesAgreement' => true, ]); $I->cantSeeResponseContainsJson([ @@ -43,7 +43,7 @@ class RegisterCest { ]); $I->wantTo('see error.username_required if username is not set'); - $route->send([ + $route->register([ 'username' => '', 'email' => '', 'password' => '', @@ -58,7 +58,7 @@ class RegisterCest { ]); $I->wantTo('don\'t see error.username_required if username is not set'); - $route->send([ + $route->register([ 'username' => 'valid_nickname', 'email' => '', 'password' => '', @@ -72,7 +72,7 @@ class RegisterCest { ]); $I->wantTo('see error.email_required if email is not set'); - $route->send([ + $route->register([ 'username' => 'valid_nickname', 'email' => '', 'password' => '', @@ -87,7 +87,7 @@ class RegisterCest { ]); $I->wantTo('see error.email_invalid if email is set, but invalid'); - $route->send([ + $route->register([ 'username' => 'valid_nickname', 'email' => 'invalid@email', 'password' => '', @@ -102,7 +102,7 @@ class RegisterCest { ]); $I->wantTo('see error.email_invalid if email is set, valid, but domain doesn\'t exist or don\'t have mx record'); - $route->send([ + $route->register([ 'username' => 'valid_nickname', 'email' => 'invalid@govnomail.com', 'password' => '', @@ -117,7 +117,7 @@ class RegisterCest { ]); $I->wantTo('see error.email_not_available if email is set, fully valid, but not available for registration'); - $route->send([ + $route->register([ 'username' => 'valid_nickname', 'email' => 'admin@ely.by', 'password' => '', @@ -132,7 +132,7 @@ class RegisterCest { ]); $I->wantTo('don\'t see errors on email if all valid'); - $route->send([ + $route->register([ 'username' => 'valid_nickname', 'email' => 'erickskrauch@ely.by', 'password' => '', @@ -142,7 +142,7 @@ class RegisterCest { $I->cantSeeResponseJsonMatchesJsonPath('$.errors.email'); $I->wantTo('see error.password_required if password is not set'); - $route->send([ + $route->register([ 'username' => 'valid_nickname', 'email' => 'erickskrauch@ely.by', 'password' => '', @@ -157,7 +157,7 @@ class RegisterCest { ]); $I->wantTo('see error.password_too_short before it will be compared with rePassword'); - $route->send([ + $route->register([ 'username' => 'valid_nickname', 'email' => 'correct-email@ely.by', 'password' => 'short', @@ -173,7 +173,7 @@ class RegisterCest { $I->cantSeeResponseJsonMatchesJsonPath('$.errors.rePassword'); $I->wantTo('see error.rePassword_required if password valid and rePassword not set'); - $route->send([ + $route->register([ 'username' => 'valid_nickname', 'email' => 'correct-email@ely.by', 'password' => 'valid-password', @@ -188,7 +188,7 @@ class RegisterCest { ]); $I->wantTo('see error.rePassword_does_not_match if password valid and rePassword donen\'t match it'); - $route->send([ + $route->register([ 'username' => 'valid_nickname', 'email' => 'correct-email@ely.by', 'password' => 'valid-password', @@ -205,10 +205,10 @@ class RegisterCest { } public function testUserCorrectRegistration(FunctionalTester $I) { - $route = new RegisterRoute($I); + $route = new SignupRoute($I); $I->wantTo('ensure that signup works'); - $route->send([ + $route->register([ 'username' => 'some_username', 'email' => 'some_email@example.com', 'password' => 'some_password',