Внедрена валидация OTP в процесс восстановления пароля

This commit is contained in:
ErickSkrauch 2017-01-23 23:50:13 +03:00
parent e82b8aa8cf
commit 4695b6e724
5 changed files with 119 additions and 24 deletions

View File

@ -2,6 +2,7 @@
namespace api\models\authentication;
use api\models\base\ApiForm;
use api\validators\TotpValidator;
use common\helpers\Error as E;
use api\traits\AccountFinder;
use common\components\UserFriendlyRandomKey;
@ -16,11 +17,16 @@ class ForgotPasswordForm extends ApiForm {
use AccountFinder;
public $login;
public $token;
public function rules() {
return [
['login', 'required', 'message' => E::LOGIN_REQUIRED],
['login', 'validateLogin'],
['token', 'required', 'when' => function(self $model) {
return !$this->hasErrors() && $model->getAccount()->is_otp_enabled;
}, 'message' => E::OTP_TOKEN_REQUIRED],
['token', 'validateTotpToken'],
['login', 'validateActivity'],
['login', 'validateFrequency'],
];
@ -34,6 +40,20 @@ class ForgotPasswordForm extends ApiForm {
}
}
public function validateTotpToken($attribute) {
if ($this->hasErrors()) {
return;
}
$account = $this->getAccount();
if (!$account->is_otp_enabled) {
return;
}
$validator = new TotpValidator(['account' => $account]);
$validator->validateAttribute($this, $attribute);
}
public function validateActivity($attribute) {
if (!$this->hasErrors()) {
$account = $this->getAccount();

View File

@ -69,9 +69,7 @@ class LoginForm extends ApiForm {
}
$validator = new TotpValidator(['account' => $account]);
if (!$validator->validate($this->token, $error)) {
$this->addError($attribute, $error);
}
$validator->validateAttribute($this, $attribute);
}
public function validateActivity($attribute) {

View File

@ -35,10 +35,11 @@ class AuthenticationRoute extends BasePage {
$this->actor->sendPOST($this->getUrl());
}
public function forgotPassword($login = '') {
public function forgotPassword($login = null, $token = null) {
$this->route = ['authentication/forgot-password'];
$this->actor->sendPOST($this->getUrl(), [
'login' => $login,
'token' => $token,
]);
}

View File

@ -1,41 +1,87 @@
<?php
namespace codeception\api\functional;
use OTPHP\TOTP;
use tests\codeception\api\_pages\AuthenticationRoute;
use tests\codeception\api\FunctionalTester;
class ForgotPasswordCest {
public function testForgotPasswordByEmail(FunctionalTester $I) {
$route = new AuthenticationRoute($I);
/**
* @var AuthenticationRoute
*/
private $route;
$I->wantTo('create new password recover request by passing email');
$route->forgotPassword('admin@ely.by');
public function _before(FunctionalTester $I) {
$this->route = new AuthenticationRoute($I);
}
public function testWrongInput(FunctionalTester $I) {
$I->wantTo('see reaction on invalid input');
$this->route->forgotPassword();
$I->canSeeResponseContainsJson([
'success' => true,
'success' => false,
'errors' => [
'login' => 'error.login_required',
],
]);
$I->canSeeResponseJsonMatchesJsonPath('$.data.canRepeatIn');
$I->canSeeResponseJsonMatchesJsonPath('$.data.repeatFrequency');
$this->route->forgotPassword('becauseimbatman!');
$I->canSeeResponseContainsJson([
'success' => false,
'errors' => [
'login' => 'error.login_not_exist',
],
]);
$this->route->forgotPassword('AccountWithEnabledOtp');
$I->canSeeResponseContainsJson([
'success' => false,
'errors' => [
'token' => 'error.token_required',
],
]);
$this->route->forgotPassword('AccountWithEnabledOtp');
$I->canSeeResponseContainsJson([
'success' => false,
'errors' => [
'token' => 'error.token_required',
],
]);
$this->route->forgotPassword('AccountWithEnabledOtp', '123456');
$I->canSeeResponseContainsJson([
'success' => false,
'errors' => [
'token' => 'error.token_incorrect',
],
]);
}
public function testForgotPasswordByEmail(FunctionalTester $I) {
$I->wantTo('create new password recover request by passing email');
$this->route->forgotPassword('admin@ely.by');
$this->assertSuccessResponse($I, false);
}
public function testForgotPasswordByUsername(FunctionalTester $I) {
$route = new AuthenticationRoute($I);
$I->wantTo('create new password recover request by passing username');
$route->forgotPassword('Admin');
$I->canSeeResponseContainsJson([
'success' => true,
]);
$I->canSeeResponseJsonMatchesJsonPath('$.data.canRepeatIn');
$I->canSeeResponseJsonMatchesJsonPath('$.data.repeatFrequency');
$I->canSeeResponseJsonMatchesJsonPath('$.data.emailMask');
$this->route->forgotPassword('Admin');
$this->assertSuccessResponse($I, true);
}
public function testForgotPasswordByAccountWithOtp(FunctionalTester $I) {
$I->wantTo('create new password recover request by passing username and otp token');
$totp = new TOTP(null, 'secret-secret-secret');
$this->route->forgotPassword('AccountWithEnabledOtp', $totp->now());
$this->assertSuccessResponse($I, true);
}
public function testDataForFrequencyError(FunctionalTester $I) {
$route = new AuthenticationRoute($I);
$I->wantTo('get info about time to repeat recover password request');
$route->forgotPassword('Notch');
$this->route->forgotPassword('Notch');
$I->canSeeResponseContainsJson([
'success' => false,
'errors' => [
@ -46,4 +92,18 @@ class ForgotPasswordCest {
$I->canSeeResponseJsonMatchesJsonPath('$.data.repeatFrequency');
}
/**
* @param FunctionalTester $I
*/
private function assertSuccessResponse(FunctionalTester $I, bool $expectEmailMask = false): void {
$I->canSeeResponseContainsJson([
'success' => true,
]);
$I->canSeeResponseJsonMatchesJsonPath('$.data.canRepeatIn');
$I->canSeeResponseJsonMatchesJsonPath('$.data.repeatFrequency');
if ($expectEmailMask) {
$I->canSeeResponseJsonMatchesJsonPath('$.data.emailMask');
}
}
}

View File

@ -4,6 +4,7 @@ namespace codeception\api\unit\models\authentication;
use api\models\authentication\ForgotPasswordForm;
use Codeception\Specify;
use common\models\EmailActivation;
use OTPHP\TOTP;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountFixture;
use tests\codeception\common\fixtures\EmailActivationFixture;
@ -18,7 +19,7 @@ class ForgotPasswordFormTest extends TestCase {
];
}
public function testValidateAccount() {
public function testValidateLogin() {
$this->specify('error.login_not_exist if login is invalid', function() {
$model = new ForgotPasswordForm(['login' => 'unexist']);
$model->validateLogin('login');
@ -32,6 +33,21 @@ class ForgotPasswordFormTest extends TestCase {
});
}
public function testValidateTotpToken() {
$model = new ForgotPasswordForm();
$model->login = 'AccountWithEnabledOtp';
$model->token = '123456';
$model->validateTotpToken('token');
$this->assertEquals(['error.token_incorrect'], $model->getErrors('token'));
$totp = new TOTP(null, 'secret-secret-secret');
$model = new ForgotPasswordForm();
$model->login = 'AccountWithEnabledOtp';
$model->token = $totp->now();
$model->validateTotpToken('token');
$this->assertEmpty($model->getErrors('token'));
}
public function testValidateActivity() {
$this->specify('error.account_not_activated if account is not confirmed', function() {
$model = new ForgotPasswordForm([