mirror of
https://github.com/elyby/accounts.git
synced 2024-12-23 13:50:06 +05:30
Описана базовая миграция, добавлена модель аккаунта, добавлена модель авторизации, написаны первичные тесты для этой модели, добавлен модуль авторизации, настроен базовый контроллер. Короче много чего сделано
This commit is contained in:
parent
841303b8ab
commit
7b650e2654
@ -13,7 +13,7 @@ return [
|
||||
'controllerNamespace' => 'api\controllers',
|
||||
'components' => [
|
||||
'user' => [
|
||||
'identityClass' => 'common\models\User',
|
||||
'identityClass' => 'common\models\Account',
|
||||
'enableAutoLogin' => true,
|
||||
],
|
||||
'log' => [
|
||||
@ -28,6 +28,17 @@ return [
|
||||
'errorHandler' => [
|
||||
'errorAction' => 'site/error',
|
||||
],
|
||||
'request' => [
|
||||
'baseUrl' => '/api',
|
||||
],
|
||||
'urlManager' => [
|
||||
'enablePrettyUrl' => true,
|
||||
'showScriptName' => false,
|
||||
'rules' => [],
|
||||
],
|
||||
],
|
||||
'modules' => [
|
||||
'login' => 'api\modules\login\Module',
|
||||
],
|
||||
'params' => $params,
|
||||
];
|
||||
|
17
api/controllers/Controller.php
Normal file
17
api/controllers/Controller.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
namespace api\controllers;
|
||||
|
||||
|
||||
class Controller extends \yii\rest\Controller {
|
||||
|
||||
public $enableCsrfValidation = true;
|
||||
|
||||
public function behaviors() {
|
||||
$parentBehaviors = parent::behaviors();
|
||||
// xml нам не понадобится
|
||||
unset($parentBehaviors['contentNegotiator']['formats']['application/xml']);
|
||||
|
||||
return $parentBehaviors;
|
||||
}
|
||||
|
||||
}
|
@ -1,17 +1,17 @@
|
||||
<?php
|
||||
namespace api\controllers;
|
||||
|
||||
use Yii;
|
||||
use common\models\LoginForm;
|
||||
use api\models\ContactForm;
|
||||
use api\models\LoginForm;
|
||||
use api\models\PasswordResetRequestForm;
|
||||
use api\models\ResetPasswordForm;
|
||||
use api\models\SignupForm;
|
||||
use api\models\ContactForm;
|
||||
use Yii;
|
||||
use yii\base\InvalidParamException;
|
||||
use yii\filters\AccessControl;
|
||||
use yii\filters\VerbFilter;
|
||||
use yii\web\BadRequestHttpException;
|
||||
use yii\web\Controller;
|
||||
use yii\filters\VerbFilter;
|
||||
use yii\filters\AccessControl;
|
||||
|
||||
/**
|
||||
* Site controller
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace common\models;
|
||||
namespace api\models;
|
||||
|
||||
use common\models\Account;
|
||||
use Yii;
|
||||
use yii\base\Model;
|
||||
|
||||
@ -65,12 +66,12 @@ class LoginForm extends Model
|
||||
/**
|
||||
* Finds user by [[username]]
|
||||
*
|
||||
* @return User|null
|
||||
* @return Account|null
|
||||
*/
|
||||
protected function getUser()
|
||||
{
|
||||
if ($this->_user === null) {
|
||||
$this->_user = User::findByUsername($this->username);
|
||||
$this->_user = Account::findByEmail($this->username);
|
||||
}
|
||||
|
||||
return $this->_user;
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace api\models;
|
||||
|
||||
use common\models\User;
|
||||
use common\models\Account;
|
||||
use yii\base\Model;
|
||||
|
||||
/**
|
||||
@ -22,7 +22,7 @@ class PasswordResetRequestForm extends Model
|
||||
['email', 'email'],
|
||||
['email', 'exist',
|
||||
'targetClass' => '\common\models\User',
|
||||
'filter' => ['status' => User::STATUS_ACTIVE],
|
||||
'filter' => ['status' => Account::STATUS_ACTIVE],
|
||||
'message' => 'There is no user with such email.'
|
||||
],
|
||||
];
|
||||
@ -35,14 +35,14 @@ class PasswordResetRequestForm extends Model
|
||||
*/
|
||||
public function sendEmail()
|
||||
{
|
||||
/* @var $user User */
|
||||
$user = User::findOne([
|
||||
'status' => User::STATUS_ACTIVE,
|
||||
/* @var $user Account */
|
||||
$user = Account::findOne([
|
||||
'status' => Account::STATUS_ACTIVE,
|
||||
'email' => $this->email,
|
||||
]);
|
||||
|
||||
if ($user) {
|
||||
if (!User::isPasswordResetTokenValid($user->password_reset_token)) {
|
||||
if (!Account::isPasswordResetTokenValid($user->password_reset_token)) {
|
||||
$user->generatePasswordResetToken();
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace api\models;
|
||||
|
||||
use common\models\User;
|
||||
use common\models\Account;
|
||||
use yii\base\InvalidParamException;
|
||||
use yii\base\Model;
|
||||
use Yii;
|
||||
@ -14,7 +14,7 @@ class ResetPasswordForm extends Model
|
||||
public $password;
|
||||
|
||||
/**
|
||||
* @var \common\models\User
|
||||
* @var \common\models\Account
|
||||
*/
|
||||
private $_user;
|
||||
|
||||
@ -31,7 +31,7 @@ class ResetPasswordForm extends Model
|
||||
if (empty($token) || !is_string($token)) {
|
||||
throw new InvalidParamException('Password reset token cannot be blank.');
|
||||
}
|
||||
$this->_user = User::findByPasswordResetToken($token);
|
||||
$this->_user = Account::findByPasswordResetToken($token);
|
||||
if (!$this->_user) {
|
||||
throw new InvalidParamException('Wrong password reset token.');
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace api\models;
|
||||
|
||||
use common\models\User;
|
||||
use common\models\Account;
|
||||
use yii\base\Model;
|
||||
use Yii;
|
||||
|
||||
@ -39,13 +39,12 @@ class SignupForm extends Model
|
||||
/**
|
||||
* Signs user up.
|
||||
*
|
||||
* @return User|null the saved model or null if saving fails
|
||||
* @return Account|null the saved model or null if saving fails
|
||||
*/
|
||||
public function signup()
|
||||
{
|
||||
if ($this->validate()) {
|
||||
$user = new User();
|
||||
$user->username = $this->username;
|
||||
$user = new Account();
|
||||
$user->email = $this->email;
|
||||
$user->setPassword($this->password);
|
||||
$user->generateAuthKey();
|
||||
|
9
api/modules/login/Module.php
Normal file
9
api/modules/login/Module.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
namespace api\modules\login;
|
||||
|
||||
|
||||
class Module extends \yii\base\Module {
|
||||
|
||||
public $id = 'login';
|
||||
|
||||
}
|
48
api/modules/login/controllers/AuthenticationController.php
Normal file
48
api/modules/login/controllers/AuthenticationController.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
namespace api\modules\login\controllers;
|
||||
|
||||
use api\controllers\Controller;
|
||||
use api\modules\login\models\AuthenticationForm;
|
||||
use Yii;
|
||||
use yii\filters\AccessControl;
|
||||
|
||||
class AuthenticationController extends Controller {
|
||||
|
||||
public function behaviors() {
|
||||
return array_merge(parent::behaviors(), [
|
||||
'access' => [
|
||||
'class' => AccessControl::className(),
|
||||
'only' => ['login-info'],
|
||||
'rules' => [
|
||||
[
|
||||
'actions' => ['login-info'],
|
||||
'allow' => true,
|
||||
'roles' => ['?'],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function verbs() {
|
||||
return [
|
||||
'loginInfo' => ['post'],
|
||||
];
|
||||
}
|
||||
|
||||
public function actionLoginInfo() {
|
||||
$model = new AuthenticationForm();
|
||||
$model->load(Yii::$app->request->post());
|
||||
if (!$model->login()) {
|
||||
return [
|
||||
'success' => false,
|
||||
'errors' => $model->getErrors(),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
12
api/modules/login/controllers/DefaultController.php
Normal file
12
api/modules/login/controllers/DefaultController.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace api\modules\login\controllers;
|
||||
|
||||
use api\controllers\Controller;
|
||||
|
||||
class DefaultController extends Controller {
|
||||
|
||||
public function actionIndex() {
|
||||
return ['hello' => 'world'];
|
||||
}
|
||||
|
||||
}
|
0
api/modules/login/models/.gitkeep
Normal file
0
api/modules/login/models/.gitkeep
Normal file
70
api/modules/login/models/AuthenticationForm.php
Normal file
70
api/modules/login/models/AuthenticationForm.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
namespace api\modules\login\models;
|
||||
|
||||
use common\models\Account;
|
||||
use Yii;
|
||||
use yii\base\Model;
|
||||
|
||||
class AuthenticationForm extends Model {
|
||||
|
||||
public $email;
|
||||
public $password;
|
||||
public $rememberMe = true;
|
||||
|
||||
private $_user;
|
||||
|
||||
public function rules() {
|
||||
return [
|
||||
['email', 'required', 'message' => 'error.email_required'],
|
||||
['email', 'email', 'message' => 'error.email_invalid'],
|
||||
['email', 'validateEmail'],
|
||||
|
||||
['password', 'required', 'message' => 'error.password_required'],
|
||||
['password', 'validatePassword'],
|
||||
|
||||
['rememberMe', 'boolean'],
|
||||
];
|
||||
}
|
||||
|
||||
public function validateEmail($attribute) {
|
||||
if (!$this->hasErrors()) {
|
||||
if ($this->getAccount() === NULL) {
|
||||
$this->addError($attribute, 'error.email_not_exist');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function validatePassword($attribute) {
|
||||
if (!$this->hasErrors()) {
|
||||
$account = $this->getAccount();
|
||||
if (!$account || !$account->validatePassword($this->password)) {
|
||||
$this->addError($attribute, 'error.password_incorrect');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs in a user using the provided username and password.
|
||||
*
|
||||
* @return boolean whether the user is logged in successfully
|
||||
*/
|
||||
public function login() {
|
||||
if (!$this->validate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Yii::$app->user->login($this->getAccount(), $this->rememberMe ? 3600 * 24 * 30 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Account|null
|
||||
*/
|
||||
protected function getAccount() {
|
||||
if ($this->_user === NULL) {
|
||||
$this->_user = Account::findByEmail($this->email);
|
||||
}
|
||||
|
||||
return $this->_user;
|
||||
}
|
||||
|
||||
}
|
@ -34,17 +34,17 @@ AppAsset::register($this);
|
||||
],
|
||||
]);
|
||||
$menuItems = [
|
||||
['label' => 'Home', 'url' => ['/site/index']],
|
||||
['label' => 'About', 'url' => ['/site/about']],
|
||||
['label' => 'Contact', 'url' => ['/site/contact']],
|
||||
['label' => 'Home', 'url' => ['site/index']],
|
||||
['label' => 'About', 'url' => ['site/about']],
|
||||
['label' => 'Contact', 'url' => ['site/contact']],
|
||||
];
|
||||
if (Yii::$app->user->isGuest) {
|
||||
$menuItems[] = ['label' => 'Signup', 'url' => ['/site/signup']];
|
||||
$menuItems[] = ['label' => 'Login', 'url' => ['/site/login']];
|
||||
$menuItems[] = ['label' => 'Signup', 'url' => ['site/signup']];
|
||||
$menuItems[] = ['label' => 'Login', 'url' => ['site/login']];
|
||||
} else {
|
||||
$menuItems[] = [
|
||||
'label' => 'Logout (' . Yii::$app->user->identity->username . ')',
|
||||
'url' => ['/site/logout'],
|
||||
'url' => ['site/logout'],
|
||||
'linkOptions' => ['data-method' => 'post']
|
||||
];
|
||||
}
|
||||
|
@ -2,10 +2,10 @@
|
||||
|
||||
/* @var $this yii\web\View */
|
||||
/* @var $form yii\bootstrap\ActiveForm */
|
||||
/* @var $model \common\models\LoginForm */
|
||||
/* @var $model \api\models\LoginForm */
|
||||
|
||||
use yii\helpers\Html;
|
||||
use yii\bootstrap\ActiveForm;
|
||||
use yii\helpers\Html;
|
||||
|
||||
$this->title = 'Login';
|
||||
$this->params['breadcrumbs'][] = $this->title;
|
||||
|
15
common/components/UserPass.php
Normal file
15
common/components/UserPass.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
namespace api\components;
|
||||
|
||||
|
||||
/**
|
||||
* Этот класс был использован для изначальной генерации паролей на Ely.by и сейчас должен быть планомерно выпилен
|
||||
* с проекта с целью заменить этот алгоритм каким-нибудь посерьёзнее.
|
||||
*/
|
||||
class UserPass {
|
||||
|
||||
public static function make ($email, $pass) {
|
||||
return md5($pass . md5(strtolower($email)));
|
||||
}
|
||||
|
||||
}
|
@ -13,5 +13,8 @@ return [
|
||||
'class' => 'yii\swiftmailer\Mailer',
|
||||
'viewPath' => '@common/mail',
|
||||
],
|
||||
'security' => [
|
||||
'passwordHashStrategy' => 'password_hash',
|
||||
]
|
||||
],
|
||||
];
|
||||
|
@ -2,12 +2,12 @@
|
||||
use yii\helpers\Html;
|
||||
|
||||
/* @var $this yii\web\View */
|
||||
/* @var $user common\models\User */
|
||||
/* @var $user common\models\Account */
|
||||
|
||||
$resetLink = Yii::$app->urlManager->createAbsoluteUrl(['site/reset-password', 'token' => $user->password_reset_token]);
|
||||
?>
|
||||
<div class="password-reset">
|
||||
<p>Hello <?= Html::encode($user->username) ?>,</p>
|
||||
<p>Hello <?= Html::encode($user->email) ?>,</p>
|
||||
|
||||
<p>Follow the link below to reset your password:</p>
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
<?php
|
||||
|
||||
/* @var $this yii\web\View */
|
||||
/* @var $user common\models\User */
|
||||
/* @var $user common\models\Account */
|
||||
|
||||
$resetLink = Yii::$app->urlManager->createAbsoluteUrl(['site/reset-password', 'token' => $user->password_reset_token]);
|
||||
?>
|
||||
Hello <?= $user->username ?>,
|
||||
Hello <?= $user->email ?>,
|
||||
|
||||
Follow the link below to reset your password:
|
||||
|
||||
|
208
common/models/Account.php
Normal file
208
common/models/Account.php
Normal file
@ -0,0 +1,208 @@
|
||||
<?php
|
||||
namespace common\models;
|
||||
|
||||
use api\components\UserPass;
|
||||
use Yii;
|
||||
use yii\base\InvalidConfigException;
|
||||
use yii\base\NotSupportedException;
|
||||
use yii\behaviors\TimestampBehavior;
|
||||
use yii\db\ActiveRecord;
|
||||
use yii\web\IdentityInterface;
|
||||
|
||||
/**
|
||||
* Поля модели:
|
||||
* @property integer $id
|
||||
* @property string $uuid
|
||||
* @property string $password_hash
|
||||
* @property integer $password_hash_strategy
|
||||
* @property string $password_reset_token
|
||||
* @property string $email
|
||||
* @property string $auth_key
|
||||
* @property integer $status
|
||||
* @property integer $created_at
|
||||
* @property integer $updated_at
|
||||
*
|
||||
* Геттеры-сеттеры:
|
||||
* @property string $password пароль пользователя (только для записи)
|
||||
*/
|
||||
class Account extends ActiveRecord implements IdentityInterface {
|
||||
|
||||
const STATUS_DELETED = -10;
|
||||
const STATUS_REGISTERED = 0;
|
||||
const STATUS_ACTIVE = 10;
|
||||
|
||||
const PASS_HASH_STRATEGY_OLD_ELY = 0;
|
||||
const PASS_HASH_STRATEGY_YII2 = 1;
|
||||
|
||||
public static function tableName() {
|
||||
return '{{%accounts}}';
|
||||
}
|
||||
|
||||
public function behaviors() {
|
||||
return [
|
||||
TimestampBehavior::className(),
|
||||
];
|
||||
}
|
||||
|
||||
public function rules() {
|
||||
return [
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public static function findIdentity($id) {
|
||||
return static::findOne(['id' => $id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public static function findIdentityByAccessToken($token, $type = null) {
|
||||
throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $email
|
||||
* @return static|null
|
||||
*/
|
||||
public static function findByEmail($email) {
|
||||
return static::findOne(['email' => $email]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds user by password reset token
|
||||
*
|
||||
* @param string $token password reset token
|
||||
*
|
||||
* @return static|null
|
||||
*
|
||||
* TODO: этот метод нужно убрать из базовой модели
|
||||
*/
|
||||
public static function findByPasswordResetToken($token) {
|
||||
if (!static::isPasswordResetTokenValid($token)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return static::findOne([
|
||||
'password_reset_token' => $token,
|
||||
'status' => self::STATUS_ACTIVE,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds out if password reset token is valid
|
||||
*
|
||||
* @param string $token password reset token
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* TODO: этот метод нужно убрать из базовой модели
|
||||
*/
|
||||
public static function isPasswordResetTokenValid($token) {
|
||||
if (empty($token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$timestamp = (int) substr($token, strrpos($token, '_') + 1);
|
||||
$expire = Yii::$app->params['user.passwordResetTokenExpire'];
|
||||
|
||||
return $timestamp + $expire >= time();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getId() {
|
||||
return $this->getPrimaryKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getAuthKey() {
|
||||
return $this->auth_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function validateAuthKey($authKey) {
|
||||
return $this->getAuthKey() === $authKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates password
|
||||
*
|
||||
* @param string $password password to validate
|
||||
* @param integer $passwordHashStrategy
|
||||
*
|
||||
* @return bool if password provided is valid for current user
|
||||
* @throws InvalidConfigException
|
||||
*/
|
||||
public function validatePassword($password, $passwordHashStrategy = NULL) {
|
||||
if ($passwordHashStrategy === NULL) {
|
||||
$passwordHashStrategy = $this->password_hash_strategy;
|
||||
}
|
||||
|
||||
switch($passwordHashStrategy) {
|
||||
case self::PASS_HASH_STRATEGY_OLD_ELY:
|
||||
$hashedPass = UserPass::make($this->email, $password);
|
||||
return $hashedPass === $this->password_hash;
|
||||
|
||||
case self::PASS_HASH_STRATEGY_YII2:
|
||||
return Yii::$app->security->validatePassword($password, $this->password_hash);
|
||||
|
||||
default:
|
||||
throw new InvalidConfigException('You must set valid password_hash_strategy before you can validate password');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $password
|
||||
* @throws InvalidConfigException
|
||||
*/
|
||||
public function setPassword($password) {
|
||||
switch($this->password_hash_strategy) {
|
||||
case self::PASS_HASH_STRATEGY_OLD_ELY:
|
||||
$password = UserPass::make($this->email, $password);
|
||||
break;
|
||||
|
||||
case self::PASS_HASH_STRATEGY_YII2:
|
||||
$password = Yii::$app->security->generatePasswordHash($password);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidConfigException('You must specify password_hash_strategy before you can set password');
|
||||
}
|
||||
|
||||
$this->password_hash = $password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates "remember me" authentication key
|
||||
*/
|
||||
public function generateAuthKey() {
|
||||
$this->auth_key = Yii::$app->security->generateRandomString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates new password reset token
|
||||
*
|
||||
* TODO: этот метод нужно отсюда убрать
|
||||
*/
|
||||
public function generatePasswordResetToken() {
|
||||
$this->password_reset_token = Yii::$app->security->generateRandomString() . '_' . time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes password reset token
|
||||
*
|
||||
* TODO: этот метод нужно отсюда убрать
|
||||
*/
|
||||
public function removePasswordResetToken() {
|
||||
$this->password_reset_token = null;
|
||||
}
|
||||
|
||||
}
|
@ -1,188 +0,0 @@
|
||||
<?php
|
||||
namespace common\models;
|
||||
|
||||
use Yii;
|
||||
use yii\base\NotSupportedException;
|
||||
use yii\behaviors\TimestampBehavior;
|
||||
use yii\db\ActiveRecord;
|
||||
use yii\web\IdentityInterface;
|
||||
|
||||
/**
|
||||
* User model
|
||||
*
|
||||
* @property integer $id
|
||||
* @property string $username
|
||||
* @property string $password_hash
|
||||
* @property string $password_reset_token
|
||||
* @property string $email
|
||||
* @property string $auth_key
|
||||
* @property integer $status
|
||||
* @property integer $created_at
|
||||
* @property integer $updated_at
|
||||
* @property string $password write-only password
|
||||
*/
|
||||
class User extends ActiveRecord implements IdentityInterface
|
||||
{
|
||||
const STATUS_DELETED = 0;
|
||||
const STATUS_ACTIVE = 10;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public static function tableName()
|
||||
{
|
||||
return '{{%user}}';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function behaviors()
|
||||
{
|
||||
return [
|
||||
TimestampBehavior::className(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
['status', 'default', 'value' => self::STATUS_ACTIVE],
|
||||
['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_DELETED]],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public static function findIdentity($id)
|
||||
{
|
||||
return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public static function findIdentityByAccessToken($token, $type = null)
|
||||
{
|
||||
throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds user by username
|
||||
*
|
||||
* @param string $username
|
||||
* @return static|null
|
||||
*/
|
||||
public static function findByUsername($username)
|
||||
{
|
||||
return static::findOne(['username' => $username, 'status' => self::STATUS_ACTIVE]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds user by password reset token
|
||||
*
|
||||
* @param string $token password reset token
|
||||
* @return static|null
|
||||
*/
|
||||
public static function findByPasswordResetToken($token)
|
||||
{
|
||||
if (!static::isPasswordResetTokenValid($token)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return static::findOne([
|
||||
'password_reset_token' => $token,
|
||||
'status' => self::STATUS_ACTIVE,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds out if password reset token is valid
|
||||
*
|
||||
* @param string $token password reset token
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isPasswordResetTokenValid($token)
|
||||
{
|
||||
if (empty($token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$timestamp = (int) substr($token, strrpos($token, '_') + 1);
|
||||
$expire = Yii::$app->params['user.passwordResetTokenExpire'];
|
||||
return $timestamp + $expire >= time();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->getPrimaryKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getAuthKey()
|
||||
{
|
||||
return $this->auth_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function validateAuthKey($authKey)
|
||||
{
|
||||
return $this->getAuthKey() === $authKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates password
|
||||
*
|
||||
* @param string $password password to validate
|
||||
* @return boolean if password provided is valid for current user
|
||||
*/
|
||||
public function validatePassword($password)
|
||||
{
|
||||
return Yii::$app->security->validatePassword($password, $this->password_hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates password hash from password and sets it to the model
|
||||
*
|
||||
* @param string $password
|
||||
*/
|
||||
public function setPassword($password)
|
||||
{
|
||||
$this->password_hash = Yii::$app->security->generatePasswordHash($password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates "remember me" authentication key
|
||||
*/
|
||||
public function generateAuthKey()
|
||||
{
|
||||
$this->auth_key = Yii::$app->security->generateRandomString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates new password reset token
|
||||
*/
|
||||
public function generatePasswordResetToken()
|
||||
{
|
||||
$this->password_reset_token = Yii::$app->security->generateRandomString() . '_' . time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes password reset token
|
||||
*/
|
||||
public function removePasswordResetToken()
|
||||
{
|
||||
$this->password_reset_token = null;
|
||||
}
|
||||
}
|
@ -17,7 +17,8 @@
|
||||
"php": ">=5.4.0",
|
||||
"yiisoft/yii2": ">=2.0.6",
|
||||
"yiisoft/yii2-bootstrap": "*",
|
||||
"yiisoft/yii2-swiftmailer": "*"
|
||||
"yiisoft/yii2-swiftmailer": "*",
|
||||
"ramsey/uuid": "^3.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"yiisoft/yii2-codeception": "*",
|
||||
|
@ -21,5 +21,11 @@ return [
|
||||
],
|
||||
],
|
||||
],
|
||||
'controllerMap' => [
|
||||
'migrate' => [
|
||||
'class' => 'yii\console\controllers\MigrateController',
|
||||
'templateFile' => '@console/views/migration.php',
|
||||
],
|
||||
],
|
||||
'params' => $params,
|
||||
];
|
||||
|
21
console/db/Migration.php
Normal file
21
console/db/Migration.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
namespace console\db;
|
||||
|
||||
use yii\db\Migration as YiiMigration;
|
||||
|
||||
/**
|
||||
* @property string $tableOptions
|
||||
*/
|
||||
class Migration extends YiiMigration {
|
||||
|
||||
public function getTableOptions($engine = 'InnoDB') {
|
||||
$tableOptions = null;
|
||||
if ($this->db->driverName === 'mysql') {
|
||||
// http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci
|
||||
$tableOptions = 'CHARACTER SET utf8 COLLATE utf8_general_ci ENGINE=' . $engine;
|
||||
}
|
||||
|
||||
return $tableOptions;
|
||||
}
|
||||
|
||||
}
|
@ -1,34 +1,26 @@
|
||||
<?php
|
||||
|
||||
use yii\db\Schema;
|
||||
use yii\db\Migration;
|
||||
use console\db\Migration;
|
||||
|
||||
class m130524_201442_init extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
$tableOptions = null;
|
||||
if ($this->db->driverName === 'mysql') {
|
||||
// http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci
|
||||
$tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
|
||||
}
|
||||
class m130524_201442_init extends Migration {
|
||||
|
||||
$this->createTable('{{%user}}', [
|
||||
public function up() {
|
||||
$this->createTable('{{%accounts}}', [
|
||||
'id' => $this->primaryKey(),
|
||||
'username' => $this->string()->notNull()->unique(),
|
||||
'auth_key' => $this->string(32)->notNull(),
|
||||
'password_hash' => $this->string()->notNull(),
|
||||
'password_reset_token' => $this->string()->unique(),
|
||||
'uuid' => $this->string(36)->notNull(),
|
||||
'email' => $this->string()->notNull()->unique(),
|
||||
|
||||
'status' => $this->smallInteger()->notNull()->defaultValue(10),
|
||||
'password_hash' => $this->string()->notNull(),
|
||||
'password_hash_strategy' => $this->smallInteger()->notNull(),
|
||||
'password_reset_token' => $this->string()->unique(),
|
||||
'status' => $this->smallInteger()->notNull()->defaultValue(0),
|
||||
'auth_key' => $this->string(32)->notNull(),
|
||||
'created_at' => $this->integer()->notNull(),
|
||||
'updated_at' => $this->integer()->notNull(),
|
||||
], $tableOptions);
|
||||
], $this->tableOptions);
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$this->dropTable('{{%user}}');
|
||||
public function down() {
|
||||
$this->dropTable('{{%accounts}}');
|
||||
}
|
||||
|
||||
}
|
||||
|
19
console/views/migration.php
Normal file
19
console/views/migration.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
/* @var $className string the new migration class name */
|
||||
|
||||
echo "<?php\n";
|
||||
?>
|
||||
|
||||
use console\db\Migration;
|
||||
|
||||
class <?= $className ?> extends Migration {
|
||||
|
||||
public function safeUp() {
|
||||
|
||||
}
|
||||
|
||||
public function safeDown() {
|
||||
|
||||
}
|
||||
|
||||
}
|
21
tests/codeception/api/_pages/LoginRoute.php
Normal file
21
tests/codeception/api/_pages/LoginRoute.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
namespace tests\codeception\api\_pages;
|
||||
|
||||
use yii\codeception\BasePage;
|
||||
|
||||
/**
|
||||
* Represents loging page
|
||||
* @property \tests\codeception\api\FunctionalTester $actor
|
||||
*/
|
||||
class LoginRoute extends BasePage {
|
||||
|
||||
public $route = 'login/authentication/login-info';
|
||||
|
||||
public function login($email, $password) {
|
||||
$this->actor->sendPOST($this->getUrl(), [
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
<?php
|
||||
use tests\codeception\api\_pages\LoginRoute;
|
||||
use tests\codeception\api\AcceptanceTester;
|
||||
use tests\codeception\common\_pages\LoginPage;
|
||||
|
||||
/* @var $scenario Codeception\Scenario */
|
||||
|
||||
$I = new AcceptanceTester($scenario);
|
||||
$I->wantTo('ensure login page works');
|
||||
|
||||
$loginPage = LoginPage::openBy($I);
|
||||
$loginPage = LoginRoute::openBy($I);
|
||||
|
||||
$I->amGoingTo('submit login form with no data');
|
||||
$loginPage->login('', '');
|
||||
|
@ -3,7 +3,7 @@
|
||||
namespace tests\codeception\api\acceptance;
|
||||
|
||||
use tests\codeception\api\_pages\SignupPage;
|
||||
use common\models\User;
|
||||
use common\models\Account;
|
||||
|
||||
class SignupCest
|
||||
{
|
||||
@ -22,7 +22,7 @@ class SignupCest
|
||||
*/
|
||||
public function _after($event)
|
||||
{
|
||||
User::deleteAll([
|
||||
Account::deleteAll([
|
||||
'email' => 'tester.email@example.com',
|
||||
'username' => 'tester',
|
||||
]);
|
||||
|
@ -12,6 +12,7 @@ modules:
|
||||
- Filesystem
|
||||
- Yii2
|
||||
- tests\codeception\common\_support\FixtureHelper
|
||||
- REST
|
||||
config:
|
||||
Yii2:
|
||||
configFile: '../config/api/functional.php'
|
||||
|
@ -1,23 +1,33 @@
|
||||
<?php
|
||||
use tests\codeception\api\_pages\LoginRoute;
|
||||
use tests\codeception\api\FunctionalTester;
|
||||
use tests\codeception\common\_pages\LoginPage;
|
||||
|
||||
/* @var $scenario Codeception\Scenario */
|
||||
|
||||
$I = new FunctionalTester($scenario);
|
||||
$I->wantTo('ensure login page works');
|
||||
|
||||
$loginPage = LoginPage::openBy($I);
|
||||
$loginPage = LoginRoute::openBy($I);
|
||||
|
||||
$I->amGoingTo('submit login form with no data');
|
||||
$loginPage->login('', '');
|
||||
$I->expectTo('see validations errors');
|
||||
$I->see('Username cannot be blank.', '.help-block');
|
||||
$I->see('Password cannot be blank.', '.help-block');
|
||||
$I->canSeeResponseContainsJson([
|
||||
'success' => false,
|
||||
'errors' => [
|
||||
'email' => [
|
||||
'error.email_required',
|
||||
],
|
||||
'password' => [
|
||||
'error.password_required',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
/*
|
||||
$I->amGoingTo('try to login with wrong credentials');
|
||||
$I->expectTo('see validations errors');
|
||||
$loginPage->login('admin', 'wrong');
|
||||
$loginPage->login('', 'wrong');
|
||||
$I->expectTo('see validations errors');
|
||||
$I->see('Incorrect username or password.', '.help-block');
|
||||
|
||||
@ -27,3 +37,4 @@ $I->expectTo('see that user is logged');
|
||||
$I->seeLink('Logout (erau)');
|
||||
$I->dontSeeLink('Login');
|
||||
$I->dontSeeLink('Signup');
|
||||
*/
|
||||
|
@ -3,7 +3,7 @@
|
||||
namespace tests\codeception\api\functional;
|
||||
|
||||
use tests\codeception\api\_pages\SignupPage;
|
||||
use common\models\User;
|
||||
use common\models\Account;
|
||||
|
||||
class SignupCest
|
||||
{
|
||||
@ -22,7 +22,7 @@ class SignupCest
|
||||
*/
|
||||
public function _after($event)
|
||||
{
|
||||
User::deleteAll([
|
||||
Account::deleteAll([
|
||||
'email' => 'tester.email@example.com',
|
||||
'username' => 'tester',
|
||||
]);
|
||||
|
15
tests/codeception/api/unit/fixtures/data/models/accounts.php
Normal file
15
tests/codeception/api/unit/fixtures/data/models/accounts.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
return [
|
||||
[
|
||||
'id' => 1,
|
||||
'uuid' => 'df936908-b2e1-544d-96f8-2977ec213022',
|
||||
'password_hash' => '$2y$13$CXT0Rkle1EMJ/c1l5bylL.EylfmQ39O5JlHJVFpNn618OUS1HwaIi', # password_0
|
||||
'password_hash_strategy' => 1,
|
||||
'password_reset_token' => NULL,
|
||||
'email' => 'admin@ely.by',
|
||||
'auth_key' => 'iwTNae9t34OmnK6l4vT4IeaTk-YWI2Rv',
|
||||
'status' => 10,
|
||||
'created_at' => 1451775316,
|
||||
'updated_at' => 1451775316,
|
||||
],
|
||||
];
|
@ -6,7 +6,7 @@ use Yii;
|
||||
use tests\codeception\api\unit\DbTestCase;
|
||||
use api\models\PasswordResetRequestForm;
|
||||
use tests\codeception\common\fixtures\UserFixture;
|
||||
use common\models\User;
|
||||
use common\models\Account;
|
||||
use Codeception\Specify;
|
||||
|
||||
class PasswordResetRequestFormTest extends DbTestCase
|
||||
@ -54,7 +54,7 @@ class PasswordResetRequestFormTest extends DbTestCase
|
||||
{
|
||||
$model = new PasswordResetRequestForm();
|
||||
$model->email = $this->user[0]['email'];
|
||||
$user = User::findOne(['password_reset_token' => $this->user[0]['password_reset_token']]);
|
||||
$user = Account::findOne(['password_reset_token' => $this->user[0]['password_reset_token']]);
|
||||
|
||||
expect('email sent', $model->sendEmail())->true();
|
||||
expect('user has valid token', $user->password_reset_token)->notNull();
|
||||
|
@ -24,7 +24,6 @@ class SignupFormTest extends DbTestCase
|
||||
|
||||
$this->assertInstanceOf('common\models\User', $user, 'user should be valid');
|
||||
|
||||
expect('username should be correct', $user->username)->equals('some_username');
|
||||
expect('email should be correct', $user->email)->equals('some_email@example.com');
|
||||
expect('password should be correct', $user->validatePassword('some_password'))->true();
|
||||
}
|
||||
|
@ -0,0 +1,124 @@
|
||||
<?php
|
||||
namespace tests\codeception\api\modules\login\models;
|
||||
|
||||
use api\modules\login\models\AuthenticationForm;
|
||||
use Codeception\Specify;
|
||||
use tests\codeception\api\unit\DbTestCase;
|
||||
use tests\codeception\common\fixtures\AccountFixture;
|
||||
use Yii;
|
||||
|
||||
class AuthenticationFormTest extends DbTestCase {
|
||||
|
||||
use Specify;
|
||||
|
||||
protected function tearDown() {
|
||||
Yii::$app->user->logout();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function testValidateEmail() {
|
||||
$model = new AuthenticationForm();
|
||||
$this->specify('error.email_required expected if email is not set', function() use ($model) {
|
||||
$model->validate(['email']);
|
||||
expect($model->getErrors('email'))->equals(['error.email_required']);
|
||||
});
|
||||
|
||||
$this->specify('error.email_invalid expected if email not correct', function() use ($model) {
|
||||
$model->email = 'wrong-email-string';
|
||||
$model->validate(['email']);
|
||||
expect($model->getErrors('email'))->equals(['error.email_invalid']);
|
||||
|
||||
$model->email = 'wrong@email';
|
||||
$model->validate(['email']);
|
||||
expect($model->getErrors('email'))->equals(['error.email_invalid']);
|
||||
});
|
||||
|
||||
$this->specify('error.email_not_exist expected if email not exists in database', function() use ($model) {
|
||||
$model->email = 'not-exist@user.com';
|
||||
$model->validate(['email']);
|
||||
expect($model->getErrors('email'))->equals(['error.email_not_exist']);
|
||||
});
|
||||
|
||||
$this->specify('no errors if email is correct and exists in database', function() use ($model) {
|
||||
$model->email = 'admin@ely.by';
|
||||
$model->validate(['email']);
|
||||
expect($model->getErrors('email'))->isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
public function testValidatePassword() {
|
||||
$model = new AuthenticationForm();
|
||||
$this->specify('error.password_required expected if password is not set', function() use ($model) {
|
||||
$model->validate(['password']);
|
||||
expect($model->getErrors('password'))->equals(['error.password_required']);
|
||||
});
|
||||
|
||||
$this->specify('error.password_incorrect expected if password not correct for passed email', function() use ($model) {
|
||||
$model->email = 'non-exist@valid.mail';
|
||||
$model->password = 'wrong-password';
|
||||
$model->validate(['password']);
|
||||
expect('if email incorrect, the error should be displayed in any case,', $model->getErrors('password'))
|
||||
->equals(['error.password_incorrect']);
|
||||
|
||||
$model->email = 'admin@ely.by';
|
||||
$model->password = 'wrong-password';
|
||||
$model->validate(['password']);
|
||||
expect($model->getErrors('password'))->equals(['error.password_incorrect']);
|
||||
});
|
||||
|
||||
$this->specify('no errors if email and password is correct and exists in database', function() use ($model) {
|
||||
$model->email = 'admin@ely.by';
|
||||
$model->password = 'password_0';
|
||||
$model->validate(['password']);
|
||||
expect($model->getErrors('password'))->isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
public function testLoginNoUser() {
|
||||
$model = new AuthenticationForm([
|
||||
'email' => 'non-exist@valid.mail',
|
||||
'password' => 'not_existing_password',
|
||||
]);
|
||||
|
||||
$this->specify('user should not be able to login, when there is no identity', function () use ($model) {
|
||||
expect('model should not login user', $model->login())->false();
|
||||
expect('user should not be logged in', Yii::$app->user->isGuest)->true();
|
||||
});
|
||||
}
|
||||
|
||||
public function testLoginWrongPassword() {
|
||||
$model = new AuthenticationForm([
|
||||
'email' => 'admin@ely.by',
|
||||
'password' => 'wrong_password',
|
||||
]);
|
||||
|
||||
$this->specify('user should not be able to login with wrong password', function () use ($model) {
|
||||
expect('model should not login user', $model->login())->false();
|
||||
expect('error message should be set', $model->errors)->hasKey('password');
|
||||
expect('user should not be logged in', Yii::$app->user->isGuest)->true();
|
||||
});
|
||||
}
|
||||
|
||||
public function testLoginCorrect() {
|
||||
$model = new AuthenticationForm([
|
||||
'email' => 'admin@ely.by',
|
||||
'password' => 'password_0',
|
||||
]);
|
||||
|
||||
$this->specify('user should be able to login with correct credentials', function () use ($model) {
|
||||
expect('model should login user', $model->login())->true();
|
||||
expect('error message should not be set', $model->errors)->hasntKey('password');
|
||||
expect('user should be logged in', Yii::$app->user->isGuest)->false();
|
||||
});
|
||||
}
|
||||
|
||||
public function fixtures() {
|
||||
return [
|
||||
'user' => [
|
||||
'class' => AccountFixture::className(),
|
||||
'dataFile' => '@tests/codeception/api/unit/fixtures/data/models/accounts.php'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace tests\codeception\common\_pages;
|
||||
|
||||
use yii\codeception\BasePage;
|
||||
|
||||
/**
|
||||
* Represents loging page
|
||||
* @property \codeception_api\AcceptanceTester|\codeception_api\FunctionalTester $actor
|
||||
*/
|
||||
class LoginPage extends BasePage
|
||||
{
|
||||
public $route = 'site/login';
|
||||
|
||||
/**
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
*/
|
||||
public function login($username, $password)
|
||||
{
|
||||
$this->actor->fillField('input[name="LoginForm[username]"]', $username);
|
||||
$this->actor->fillField('input[name="LoginForm[password]"]', $password);
|
||||
$this->actor->click('login-button');
|
||||
}
|
||||
}
|
@ -63,10 +63,10 @@ class FixtureHelper extends Module
|
||||
public function fixtures()
|
||||
{
|
||||
return [
|
||||
'user' => [
|
||||
'class' => UserFixture::className(),
|
||||
'dataFile' => '@tests/codeception/common/fixtures/data/init_login.php',
|
||||
],
|
||||
//'user' => [
|
||||
// 'class' => UserFixture::className(),
|
||||
// 'dataFile' => '@tests/codeception/common/fixtures/data/init_login.php',
|
||||
//],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
12
tests/codeception/common/fixtures/AccountFixture.php
Normal file
12
tests/codeception/common/fixtures/AccountFixture.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace tests\codeception\common\fixtures;
|
||||
|
||||
use common\models\Account;
|
||||
use yii\test\ActiveFixture;
|
||||
|
||||
class AccountFixture extends ActiveFixture {
|
||||
|
||||
public $modelClass = Account::class;
|
||||
|
||||
}
|
@ -2,11 +2,11 @@
|
||||
|
||||
namespace tests\codeception\common\unit\models;
|
||||
|
||||
use Yii;
|
||||
use tests\codeception\common\unit\DbTestCase;
|
||||
use api\models\LoginForm;
|
||||
use Codeception\Specify;
|
||||
use common\models\LoginForm;
|
||||
use tests\codeception\common\fixtures\UserFixture;
|
||||
use tests\codeception\common\unit\DbTestCase;
|
||||
use Yii;
|
||||
|
||||
/**
|
||||
* Login form test
|
||||
|
@ -14,7 +14,7 @@ return [
|
||||
],
|
||||
'components' => [
|
||||
'db' => [
|
||||
'dsn' => 'mysql:host=localhost;dbname=yii2_advanced_tests',
|
||||
'dsn' => 'mysql:host=localhost;dbname=ely_accounts_test',
|
||||
],
|
||||
'mailer' => [
|
||||
'useFileTransport' => true,
|
||||
|
Loading…
Reference in New Issue
Block a user