Skip to content

Commit 0a96a7e

Browse files
author
Sergey Mochalov
committed
User model
1 parent 6df8470 commit 0a96a7e

File tree

1 file changed

+233
-54
lines changed

1 file changed

+233
-54
lines changed

models/User.php

Lines changed: 233 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,283 @@
11
<?php
22

3+
declare(strict_types=1);
4+
35
namespace app\models;
46

5-
class User extends \yii\base\BaseObject implements \yii\web\IdentityInterface
6-
{
7-
public $id;
8-
public $username;
9-
public $password;
10-
public $authKey;
11-
public $accessToken;
7+
use Yii;
8+
use yii\db\ActiveRecord;
9+
use yii\web\IdentityInterface;
10+
use yii\behaviors\TimestampBehavior;
1211

13-
private static $users = [
14-
'100' => [
15-
'id' => '100',
16-
'username' => 'admin',
17-
'password' => 'admin',
18-
'authKey' => 'test100key',
19-
'accessToken' => '100-token',
20-
],
21-
'101' => [
22-
'id' => '101',
23-
'username' => 'demo',
24-
'password' => 'demo',
25-
'authKey' => 'test101key',
26-
'accessToken' => '101-token',
27-
],
28-
];
12+
/**
13+
* User model
14+
*
15+
* @property integer $id
16+
* @property string $username
17+
* @property string $email
18+
* @property string $password_hash
19+
* @property string $auth_key
20+
* @property string $access_token
21+
* @property string $password_reset_token
22+
* @property integer $created_at
23+
* @property integer $updated_at
24+
* @property integer $status
25+
*/
26+
class User extends ActiveRecord implements IdentityInterface
27+
{
28+
// Константы статусов
29+
const STATUS_INACTIVE = 0;
30+
const STATUS_ACTIVE = 10;
31+
32+
// Свойства для работы с паролями
33+
public ?string $password = null;
34+
35+
/**
36+
* {@inheritdoc}
37+
*/
38+
public static function tableName(): string
39+
{
40+
return '{{%user}}';
41+
}
42+
43+
/**
44+
* {@inheritdoc}
45+
*/
46+
public function behaviors(): array
47+
{
48+
return [
49+
TimestampBehavior::class,
50+
];
51+
}
52+
53+
/**
54+
* {@inheritdoc}
55+
*/
56+
public function rules(): array
57+
{
58+
return [
59+
['username', 'trim'],
60+
['username', 'required'],
61+
['username', 'unique', 'targetClass' => '\app\models\User', 'message' => 'This username has already been taken.'],
62+
['username', 'string', 'min' => 2, 'max' => 255],
2963

64+
['email', 'trim'],
65+
['email', 'required'],
66+
['email', 'email'],
67+
['email', 'string', 'max' => 255],
68+
['email', 'unique', 'targetClass' => '\app\models\User', 'message' => 'This email address has already been taken.'],
3069

70+
['password', 'required', 'on' => 'create'],
71+
['password', 'string', 'min' => 6],
72+
73+
['status', 'default', 'value' => self::STATUS_ACTIVE],
74+
['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_INACTIVE]],
75+
];
76+
}
77+
3178
/**
3279
* {@inheritdoc}
3380
*/
34-
public static function findIdentity($id)
81+
public function attributeLabels(): array
3582
{
36-
return isset(self::$users[$id]) ? new static(self::$users[$id]) : null;
83+
return [
84+
'id' => 'ID',
85+
'username' => 'Username',
86+
'email' => 'Email',
87+
'password' => 'Password',
88+
'created_at' => 'Created At',
89+
'updated_at' => 'Updated At',
90+
'status' => 'Status',
91+
];
3792
}
38-
93+
3994
/**
4095
* {@inheritdoc}
4196
*/
42-
public static function findIdentityByAccessToken($token, $type = null)
97+
public static function findIdentity($id): ?self
4398
{
44-
foreach (self::$users as $user) {
45-
if ($user['accessToken'] === $token) {
46-
return new static($user);
47-
}
48-
}
49-
50-
return null;
99+
return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]);
51100
}
52-
101+
102+
/**
103+
* {@inheritdoc}
104+
*/
105+
public static function findIdentityByAccessToken($token, $type = null): ?self
106+
{
107+
return static::findOne(['access_token' => $token, 'status' => self::STATUS_ACTIVE]);
108+
}
109+
53110
/**
54111
* Finds user by username
55112
*
56113
* @param string $username
57114
* @return static|null
58115
*/
59-
public static function findByUsername($username)
116+
public static function findByUsername(string $username): ?self
60117
{
61-
foreach (self::$users as $user) {
62-
if (strcasecmp($user['username'], $username) === 0) {
63-
return new static($user);
64-
}
65-
}
66-
67-
return null;
118+
return static::findOne(['username' => $username, 'status' => self::STATUS_ACTIVE]);
68119
}
69-
120+
70121
/**
71122
* {@inheritdoc}
72123
*/
73-
public function getId()
124+
public function getId(): int
74125
{
75126
return $this->id;
76127
}
77-
128+
78129
/**
79130
* {@inheritdoc}
80131
*/
81-
public function getAuthKey()
132+
public function getAuthKey(): string
82133
{
83-
return $this->authKey;
134+
return $this->auth_key;
84135
}
85-
136+
86137
/**
87138
* {@inheritdoc}
88139
*/
89-
public function validateAuthKey($authKey)
140+
public function validateAuthKey($authKey): bool
90141
{
91-
return $this->authKey === $authKey;
142+
return $this->auth_key === $authKey;
92143
}
93-
144+
94145
/**
95146
* Validates password
96147
*
97148
* @param string $password password to validate
98149
* @return bool if password provided is valid for current user
99150
*/
100-
public function validatePassword($password)
151+
public function validatePassword(string $password): bool
101152
{
102-
return $this->password === $password;
153+
return Yii::$app->security->validatePassword($password, $this->password_hash);
154+
}
155+
156+
/**
157+
* Generates password hash from password and sets it to the model
158+
*
159+
* @param string $password
160+
*/
161+
public function setPassword(string $password): void
162+
{
163+
$this->password_hash = Yii::$app->security->generatePasswordHash($password);
164+
}
165+
166+
/**
167+
* Generates "remember me" authentication key
168+
*/
169+
public function generateAuthKey()
170+
{
171+
$this->auth_key = Yii::$app->security->generateRandomString();
172+
}
173+
174+
/**
175+
* Generates access token for API
176+
*/
177+
public function generateAccessToken(): void
178+
{
179+
$this->access_token = Yii::$app->security->generateRandomString(255);
180+
}
181+
182+
/**
183+
* Generates new password reset token
184+
*/
185+
public function generatePasswordResetToken(): void
186+
{
187+
$this->password_reset_token = Yii::$app->security->generateRandomString() . '_' . time();
188+
}
189+
190+
/**
191+
* Removes password reset token
192+
*/
193+
public function removePasswordResetToken(): void
194+
{
195+
$this->password_reset_token = null;
196+
}
197+
198+
/**
199+
* Finds user by password reset token
200+
*
201+
* @param string $token password reset token
202+
* @return static|null
203+
*/
204+
public static function findByPasswordResetToken(string $token): ?self
205+
{
206+
if (!static::isPasswordResetTokenValid($token)) {
207+
return null;
208+
}
209+
210+
return static::findOne([
211+
'password_reset_token' => $token,
212+
'status' => self::STATUS_ACTIVE,
213+
]);
214+
}
215+
216+
/**
217+
* Finds out if password reset token is valid
218+
*
219+
* @param string $token password reset token
220+
* @return bool
221+
*/
222+
public static function isPasswordResetTokenValid(string $token): bool
223+
{
224+
if (empty($token)) {
225+
return false;
226+
}
227+
228+
$timestamp = (int) substr($token, strrpos($token, '_') + 1);
229+
$expire = Yii::$app->params['user.passwordResetTokenExpire'];
230+
return $timestamp + $expire >= time();
231+
}
232+
233+
/**
234+
* Проверяет, заблокирована ли учетная запись
235+
*
236+
* @return bool
237+
*/
238+
public function isAccountLocked(): bool
239+
{
240+
return $this->locked_until !== null && $this->locked_until > time();
241+
}
242+
243+
/**
244+
* Обрабатывает успешный вход пользователя
245+
*/
246+
public function handleSuccessfulLogin()
247+
{
248+
$this->failed_login_attempts = 0;
249+
$this->last_failed_login_at = null;
250+
$this->locked_until = null;
251+
$this->save(false); // Не валидируем, так как это внутреннее обновление
252+
}
253+
254+
/**
255+
* Обрабатывает неудачный вход пользователя
256+
*/
257+
public function handleFailedLogin()
258+
{
259+
$this->failed_login_attempts++;
260+
$this->last_failed_login_at = time();
261+
262+
// Если попыток больше 5, блокируем учетную запись на 1 час
263+
if ($this->failed_login_attempts >= 5) {
264+
$this->locked_until = time() + 3600; // Блокировка на 1 час
265+
}
266+
267+
$this->save(false); // Не валидируем, так как это внутреннее обновление
268+
}
269+
270+
/**
271+
* Возвращает время оставшейся блокировки в секундах
272+
*
273+
* @return int
274+
*/
275+
public function getRemainingLockTime(): int
276+
{
277+
if (!$this->isAccountLocked()) {
278+
return 0;
279+
}
280+
281+
return $this->locked_until - time();
103282
}
104283
}

0 commit comments

Comments
 (0)