From b9ee667829d1b1e9d51a26fff01e62790e3ea86f Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sun, 13 Mar 2016 02:19:00 +0300 Subject: [PATCH 1/3] =?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', From 7343a3b506c8329620615c51482ffbc414b17878 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sun, 13 Mar 2016 21:24:49 +0300 Subject: [PATCH 2/3] =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D1=80=D0=B5=D1=84=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D0=BE=D1=80=D0=B5=D0=BD=D0=B0=20=D1=84=D0=BE=D1=80?= =?UTF-8?q?=D0=BC=D0=B0=20=D0=BE=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=BD=D0=BE=D0=B2=D0=BE=D0=B3=D0=BE=20=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D1=8C=D0=BC=D0=B0=20=D1=81=20=D0=B0=D0=BA=D1=82=D0=B8=D0=B2?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B5=D0=B9=20=D0=B0=D0=BA=D0=BA=D0=B0=D1=83?= =?UTF-8?q?=D0=BD=D1=82=D0=B0,=20=D0=B4=D0=BE=D0=BF=D0=B8=D1=81=D0=B0?= =?UTF-8?q?=D0=BD=D1=8B=20=D1=8E=D0=BD=D0=B8=D1=82=20=D0=B8=20=D1=84=D1=83?= =?UTF-8?q?=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD=D0=B0=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D1=8B=D0=B5=20=D1=82=D0=B5=D1=81=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/controllers/SignupController.php | 16 +-- api/exceptions/Exception.php | 6 + .../ThisShouldNotHaveHappenedException.php | 11 ++ ...rm.php => RepeatAccountActivationForm.php} | 30 +++-- tests/codeception/api/_pages/SignupRoute.php | 4 +- .../functional/NewAccountActivationCest.php | 21 ---- .../RepeatAccountActivationCest.php | 71 ++++++++++++ .../RepeatAccountActivationFormTest.php | 106 ++++++++++++++++++ .../common/fixtures/data/accounts.php | 15 ++- .../fixtures/data/email-activations.php | 6 + 10 files changed, 238 insertions(+), 48 deletions(-) create mode 100644 api/exceptions/Exception.php create mode 100644 api/exceptions/ThisShouldNotHaveHappenedException.php rename api/models/{NewAccountActivationForm.php => RepeatAccountActivationForm.php} (76%) delete mode 100644 tests/codeception/api/functional/NewAccountActivationCest.php create mode 100644 tests/codeception/api/functional/RepeatAccountActivationCest.php create mode 100644 tests/codeception/api/unit/models/RepeatAccountActivationFormTest.php diff --git a/api/controllers/SignupController.php b/api/controllers/SignupController.php index dcb73b9..f29cff5 100644 --- a/api/controllers/SignupController.php +++ b/api/controllers/SignupController.php @@ -2,7 +2,7 @@ namespace api\controllers; use api\models\ConfirmEmailForm; -use api\models\NewAccountActivationForm; +use api\models\RepeatAccountActivationForm; use api\models\RegistrationForm; use Yii; use yii\filters\AccessControl; @@ -13,13 +13,13 @@ class SignupController extends Controller { public function behaviors() { return ArrayHelper::merge(parent::behaviors(), [ 'authenticator' => [ - 'except' => ['index', 'new-message', 'confirm'], + 'except' => ['index', 'repeat-message', 'confirm'], ], 'access' => [ 'class' => AccessControl::class, 'rules' => [ [ - 'actions' => ['index', 'new-message', 'confirm'], + 'actions' => ['index', 'repeat-message', 'confirm'], 'allow' => true, 'roles' => ['?'], ], @@ -51,10 +51,10 @@ class SignupController extends Controller { ]; } - public function actionNewMessage() { - $model = new NewAccountActivationForm(); + public function actionRepeatMessage() { + $model = new RepeatAccountActivationForm(); $model->load(Yii::$app->request->post()); - if (!$model->sendNewMessage()) { + if (!$model->sendRepeatMessage()) { $response = [ 'success' => false, 'errors' => $this->normalizeModelErrors($model->getErrors()), @@ -63,8 +63,8 @@ class SignupController extends Controller { 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, + 'canRepeatIn' => $activeActivation->created_at - time() + RepeatAccountActivationForm::REPEAT_FREQUENCY, + 'repeatFrequency' => RepeatAccountActivationForm::REPEAT_FREQUENCY, ]; } diff --git a/api/exceptions/Exception.php b/api/exceptions/Exception.php new file mode 100644 index 0000000..50972ae --- /dev/null +++ b/api/exceptions/Exception.php @@ -0,0 +1,6 @@ + 'trim'], ['email', 'required', 'message' => 'error.email_required'], - ['email', 'validateAccountForEmail'], + ['email', 'validateEmailForAccount'], ['email', 'validateExistsActivation'], ]; } - public function validateAccountForEmail($attribute) { + public function validateEmailForAccount($attribute) { if (!$this->hasErrors($attribute)) { $account = $this->getAccount(); - if ($account && $account->status === Account::STATUS_ACTIVE) { - $this->addError($attribute, "error.account_already_activated"); - } elseif (!$account) { + if ($account === null) { $this->addError($attribute, "error.{$attribute}_not_found"); + } elseif ($account->status === Account::STATUS_ACTIVE) { + $this->addError($attribute, "error.account_already_activated"); + } elseif ($account->status !== Account::STATUS_REGISTERED) { + // TODO: такие аккаунты следует логировать за попытку к саботажу + $this->addError($attribute, "error.account_cannot_resend_message"); } } } @@ -42,7 +45,7 @@ class NewAccountActivationForm extends BaseApiForm { } } - public function sendNewMessage() { + public function sendRepeatMessage() { if (!$this->validate()) { return false; } @@ -50,15 +53,10 @@ class NewAccountActivationForm extends BaseApiForm { $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(); - } + EmailActivation::deleteAll([ + 'account_id' => $account->id, + 'type' => EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION, + ]); $activation = new EmailActivation(); $activation->account_id = $account->id; diff --git a/tests/codeception/api/_pages/SignupRoute.php b/tests/codeception/api/_pages/SignupRoute.php index 6f796bd..a74c460 100644 --- a/tests/codeception/api/_pages/SignupRoute.php +++ b/tests/codeception/api/_pages/SignupRoute.php @@ -13,8 +13,8 @@ class SignupRoute extends BasePage { $this->actor->sendPOST($this->getUrl(), $registrationData); } - public function sendNewMessage($email = '') { - $this->route = ['signup/new-message']; + public function sendRepeatMessage($email = '') { + $this->route = ['signup/repeat-message']; $this->actor->sendPOST($this->getUrl(), ['email' => $email]); } diff --git a/tests/codeception/api/functional/NewAccountActivationCest.php b/tests/codeception/api/functional/NewAccountActivationCest.php deleted file mode 100644 index a4291ce..0000000 --- a/tests/codeception/api/functional/NewAccountActivationCest.php +++ /dev/null @@ -1,21 +0,0 @@ -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/RepeatAccountActivationCest.php b/tests/codeception/api/functional/RepeatAccountActivationCest.php new file mode 100644 index 0000000..8f24d67 --- /dev/null +++ b/tests/codeception/api/functional/RepeatAccountActivationCest.php @@ -0,0 +1,71 @@ +wantTo('error.email_required on empty for submitting'); + $route->sendRepeatMessage(); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'email' => 'error.email_required', + ], + ]); + + $I->wantTo('error.email_not_found if email is not presented in db'); + $route->sendRepeatMessage('im-not@exists.net'); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'email' => 'error.email_not_found', + ], + ]); + + $I->wantTo('error.account_already_activated if passed email matches with already activated account'); + $route->sendRepeatMessage('admin@ely.by'); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'email' => 'error.account_already_activated', + ], + ]); + + $I->wantTo('error.recently_sent_message if last message was send too recently'); + $route->sendRepeatMessage('achristiansen@gmail.com'); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'email' => 'error.recently_sent_message', + ], + ]); + $I->canSeeResponseJsonMatchesJsonPath('$.data.canRepeatIn'); + $I->canSeeResponseJsonMatchesJsonPath('$.data.repeatFrequency'); + } + + public function testSuccess(FunctionalTester $I) { + $route = new SignupRoute($I); + + $I->wantTo('successfully resend account activation message'); + $route->sendRepeatMessage('jon@ely.by'); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson(['success' => true]); + $I->cantSeeResponseJsonMatchesJsonPath('$.errors'); + } + +} diff --git a/tests/codeception/api/unit/models/RepeatAccountActivationFormTest.php b/tests/codeception/api/unit/models/RepeatAccountActivationFormTest.php new file mode 100644 index 0000000..07ebe07 --- /dev/null +++ b/tests/codeception/api/unit/models/RepeatAccountActivationFormTest.php @@ -0,0 +1,106 @@ +mailer; + $mailer->fileTransportCallback = function () { + return 'testing_message.eml'; + }; + } + + protected function tearDown() { + if (file_exists($this->getMessageFile())) { + unlink($this->getMessageFile()); + } + + parent::tearDown(); + } + + public function fixtures() { + return [ + 'accounts' => [ + 'class' => AccountFixture::class, + 'dataFile' => '@tests/codeception/common/fixtures/data/accounts.php', + ], + 'activations' => [ + 'class' => EmailActivationFixture::class, + 'dataFile' => '@tests/codeception/common/fixtures/data/email-activations.php', + ], + ]; + } + + public function testValidateEmailForAccount() { + $this->specify('error.email_not_found if passed valid email, but it don\'t exists in database', function() { + $model = new RepeatAccountActivationForm(['email' => 'me-is-not@exists.net']); + $model->validateEmailForAccount('email'); + expect($model->getErrors('email'))->equals(['error.email_not_found']); + }); + + $this->specify('error.account_already_activated if passed valid email, but account already activated', function() { + $model = new RepeatAccountActivationForm(['email' => $this->accounts['admin']['email']]); + $model->validateEmailForAccount('email'); + expect($model->getErrors('email'))->equals(['error.account_already_activated']); + }); + + $this->specify('no errors if passed valid email for not activated account', function() { + $model = new RepeatAccountActivationForm(['email' => $this->accounts['not-activated-account']['email']]); + $model->validateEmailForAccount('email'); + expect($model->getErrors('email'))->isEmpty(); + }); + } + + public function testValidateExistsActivation() { + $this->specify('error.recently_sent_message if passed email has recently sent message', function() { + $model = new RepeatAccountActivationForm(['email' => $this->accounts['not-activated-account']['email']]); + $model->validateExistsActivation('email'); + expect($model->getErrors('email'))->equals(['error.recently_sent_message']); + }); + + $this->specify('no errors if passed email has expired activation message', function() { + $email = $this->accounts['not-activated-account-with-expired-message']['email']; + $model = new RepeatAccountActivationForm(['email' => $email]); + $model->validateExistsActivation('email'); + expect($model->getErrors('email'))->isEmpty(); + }); + } + + public function testSendRepeatMessage() { + $this->specify('no magic if we don\'t pass validation', function() { + $model = new RepeatAccountActivationForm(); + expect($model->sendRepeatMessage())->false(); + expect_file($this->getMessageFile())->notExists(); + }); + + $this->specify('successfully send new message if previous message has expired', function() { + $email = $this->accounts['not-activated-account-with-expired-message']['email']; + $model = new RepeatAccountActivationForm(['email' => $email]); + expect($model->sendRepeatMessage())->true(); + expect($model->getActiveActivation())->notNull(); + expect_file($this->getMessageFile())->exists(); + }); + } + + private function getMessageFile() { + /** @var \yii\swiftmailer\Mailer $mailer */ + $mailer = Yii::$app->mailer; + + return Yii::getAlias($mailer->fileTransportPath) . '/testing_message.eml'; + } + +} diff --git a/tests/codeception/common/fixtures/data/accounts.php b/tests/codeception/common/fixtures/data/accounts.php index eb70aef..fe8bf7a 100644 --- a/tests/codeception/common/fixtures/data/accounts.php +++ b/tests/codeception/common/fixtures/data/accounts.php @@ -38,5 +38,18 @@ return [ 'status' => \common\models\Account::STATUS_REGISTERED, 'created_at' => 1453146616, 'updated_at' => 1453146616, - ] + ], + 'not-activated-account-with-expired-message' => [ + 'id' => 4, + 'uuid' => '58a7bfdc-ad0f-44c3-9197-759cb9220895', + 'username' => 'Jon', + 'email' => 'jon@ely.by', + 'password_hash' => '$2y$13$2rYkap5T6jG8z/mMK8a3Ou6aZxJcmAaTha6FEuujvHEmybSHRzW5e', # password_0 + 'password_hash_strategy' => \common\models\Account::PASS_HASH_STRATEGY_YII2, + 'password_reset_token' => null, + 'auth_key' => '45DsaEQ7U8lU9umIyCWk5iCnpdPvZ8Up', + 'status' => \common\models\Account::STATUS_REGISTERED, + 'created_at' => 1457890086, + 'updated_at' => 1457890086, + ], ]; diff --git a/tests/codeception/common/fixtures/data/email-activations.php b/tests/codeception/common/fixtures/data/email-activations.php index f0a859c..09c1aed 100644 --- a/tests/codeception/common/fixtures/data/email-activations.php +++ b/tests/codeception/common/fixtures/data/email-activations.php @@ -6,4 +6,10 @@ return [ 'type' => \common\models\EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION, 'created_at' => time(), ], + [ + 'key' => 'H23HBDCHHAG2HGHGHS', + 'account_id' => 4, + 'type' => \common\models\EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION, + 'created_at' => time() - \api\models\RepeatAccountActivationForm::REPEAT_FREQUENCY - 10, + ], ]; From 6b04860f0efd9e8283f3f07d282239f43bd43f67 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sun, 13 Mar 2016 21:46:22 +0300 Subject: [PATCH 3/3] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=D0=B0=D1=87=D0=B0?= =?UTF-8?q?=20email=20=D0=BF=D1=80=D0=B8=20=D0=B2=D1=85=D0=BE=D0=B4=D0=B5?= =?UTF-8?q?=20=D0=B2=20=D0=BD=D0=B5=D0=B0=D0=BA=D1=82=D0=B8=D0=B2=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=BD=D1=8B=D0=B9=20=D0=B0=D0=BA?= =?UTF-8?q?=D0=BA=D0=B0=D1=83=D0=BD=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/controllers/AuthenticationController.php | 8 +++++++- api/models/LoginForm.php | 2 +- tests/codeception/api/functional/LoginCest.php | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/api/controllers/AuthenticationController.php b/api/controllers/AuthenticationController.php index f6f5240..5d0ba23 100644 --- a/api/controllers/AuthenticationController.php +++ b/api/controllers/AuthenticationController.php @@ -36,10 +36,16 @@ class AuthenticationController extends Controller { $model = new LoginForm(); $model->load(Yii::$app->request->post()); if (($jwt = $model->login()) === false) { - return [ + $data = [ 'success' => false, 'errors' => $this->normalizeModelErrors($model->getErrors()), ]; + + if (ArrayHelper::getValue($data['errors'], 'login') === 'error.account_not_activated') { + $data['data']['email'] = $model->getAccount()->email; + } + + return $data; } return [ diff --git a/api/models/LoginForm.php b/api/models/LoginForm.php index 3f9db42..a123560 100644 --- a/api/models/LoginForm.php +++ b/api/models/LoginForm.php @@ -68,7 +68,7 @@ class LoginForm extends BaseApiForm { /** * @return Account|null */ - protected function getAccount() { + public function getAccount() { if ($this->_account === NULL) { $attribute = strpos($this->login, '@') ? 'email' : 'username'; $this->_account = Account::findOne([$attribute => $this->login]); diff --git a/tests/codeception/api/functional/LoginCest.php b/tests/codeception/api/functional/LoginCest.php index adff510..73c6eb8 100644 --- a/tests/codeception/api/functional/LoginCest.php +++ b/tests/codeception/api/functional/LoginCest.php @@ -43,6 +43,7 @@ class LoginCest { 'login' => 'error.account_not_activated', ], ]); + $I->canSeeResponseJsonMatchesJsonPath('$.data.email'); $I->wantTo('don\'t see errors on login field if username is correct and exists in database'); $route->login('Admin');