Files
2026-03-30 11:47:32 +08:00

362 lines
11 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace app\common\library;
use Throwable;
use ba\Random;
use support\think\Db;
use app\common\model\User;
use app\common\facade\Token;
use Webman\Http\Request;
/**
* 公共权限类(会员权限类)
*/
class Auth extends \ba\Auth
{
public const LOGIN_RESPONSE_CODE = 303;
public const NEED_LOGIN = 'need login';
public const LOGGED_IN = 'logged in';
public const TOKEN_TYPE = 'user';
/**
* 积分商城用户mall_playx_user_asset 主键Token 类型,与会员 user 表区分
*/
public const TOKEN_TYPE_MALL_USER = 'muser';
protected bool $loginEd = false;
protected string $error = '';
protected ?User $model = null;
protected string $token = '';
protected string $refreshToken = '';
protected int $keepTime = 86400;
protected int $refreshTokenKeepTime = 2592000;
protected array $allowFields = ['id', 'username', 'nickname', 'email', 'mobile', 'avatar', 'gender', 'birthday', 'money', 'score', 'join_time', 'motto', 'last_login_time', 'last_login_ip'];
public function __construct(array $config = [])
{
parent::__construct(array_merge([
'auth_group' => 'user_group',
'auth_group_access' => '',
'auth_rule' => 'user_rule',
], $config));
$this->setKeepTime((int)config('buildadmin.user_token_keep_time', 86400));
}
public function __get($name): mixed
{
return $this->model?->$name;
}
public static function instance(array $options = []): Auth
{
$request = $options['request'] ?? (function_exists('request') ? request() : null);
unset($options['request']);
if ($request && !isset($request->userAuth)) {
$request->userAuth = new static($options);
}
return $request && isset($request->userAuth) ? $request->userAuth : new static($options);
}
public function init($token): bool
{
$tokenData = Token::get($token);
if ($tokenData) {
Token::tokenExpirationCheck($tokenData);
$userId = $tokenData['user_id'];
if ($tokenData['type'] == self::TOKEN_TYPE && $userId > 0) {
$this->model = User::where('id', $userId)->find();
if (!$this->model) {
$this->setError('Account not exist');
return false;
}
if ($this->model->status != 'enable') {
$this->setError('Account disabled');
return false;
}
$this->token = $token;
$this->loginSuccessful();
return true;
}
}
$this->setError('Token login failed');
$this->reset();
return false;
}
public function register(string $username, string $password = '', string $mobile = '', string $email = '', int $group = 1, array $extend = []): bool
{
$request = function_exists('request') ? request() : null;
$ip = $request ? $request->getRealIp() : '0.0.0.0';
if ($email && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$this->setError(__('Email'));
return false;
}
if ($username && !preg_match('/^[a-zA-Z][a-zA-Z0-9_]{2,15}$/', $username)) {
$this->setError(__('Username'));
return false;
}
if (User::where('email', $email)->find() && $email) {
$this->setError(__('Email') . ' ' . __('already exists'));
return false;
}
if (User::where('username', $username)->find()) {
$this->setError(__('Username') . ' ' . __('already exists'));
return false;
}
$nickname = preg_replace_callback('/1[3-9]\d{9}/', fn($m) => substr($m[0], 0, 3) . '****' . substr($m[0], 7), $username);
$time = time();
$data = [
'group_id' => $group,
'nickname' => $nickname,
'join_ip' => $ip,
'join_time' => $time,
'last_login_ip' => $ip,
'last_login_time' => $time,
'status' => 'enable',
];
$data = array_merge(compact('username', 'password', 'mobile', 'email'), $data, $extend);
Db::startTrans();
try {
$this->model = User::create($data);
$this->token = Random::uuid();
Token::set($this->token, self::TOKEN_TYPE, $this->model->id, $this->keepTime);
Db::commit();
if ($password) {
$this->model->resetPassword($this->model->id, $password);
}
event_trigger('userRegisterSuccess', $this->model);
} catch (Throwable $e) {
$this->setError($e->getMessage());
Db::rollback();
return false;
}
return true;
}
public function login(string $username, string $password, bool $keep): bool
{
$accountType = false;
if (preg_match('/^1[3-9]\d{9}$/', $username)) $accountType = 'mobile';
elseif (filter_var($username, FILTER_VALIDATE_EMAIL)) $accountType = 'email';
elseif (preg_match('/^[a-zA-Z][a-zA-Z0-9_]{2,15}$/', $username)) $accountType = 'username';
if (!$accountType) {
$this->setError('Account not exist');
return false;
}
$this->model = User::where($accountType, $username)->find();
if (!$this->model) {
$this->setError('Account not exist');
return false;
}
if ($this->model->status == 'disable') {
$this->setError('Account disabled');
return false;
}
$userLoginRetry = config('buildadmin.user_login_retry');
if ($userLoginRetry && $this->model->last_login_time) {
$lastLoginTs = is_numeric($this->model->last_login_time) ? (int)$this->model->last_login_time : strtotime($this->model->last_login_time);
if ($this->model->login_failure > 0 && $lastLoginTs > 0 && time() - $lastLoginTs >= 86400) {
$this->model->login_failure = 0;
$this->model->save();
$this->model = User::where($accountType, $username)->find();
}
if ($this->model->login_failure >= $userLoginRetry) {
$this->setError('Please try again after 1 day');
return false;
}
}
if (!verify_password($password, $this->model->password, ['salt' => $this->model->salt])) {
$this->loginFailed();
$this->setError('Password is incorrect');
return false;
}
if (config('buildadmin.user_sso')) {
Token::clear(self::TOKEN_TYPE, $this->model->id);
Token::clear(self::TOKEN_TYPE . '-refresh', $this->model->id);
}
if ($keep) $this->setRefreshToken($this->refreshTokenKeepTime);
return $this->loginSuccessful();
}
public function direct(int $userId): bool
{
$this->model = User::find($userId);
if (!$this->model) return false;
if (config('buildadmin.user_sso')) {
Token::clear(self::TOKEN_TYPE, $this->model->id);
Token::clear(self::TOKEN_TYPE . '-refresh', $this->model->id);
}
return $this->loginSuccessful();
}
public function loginSuccessful(): bool
{
if (!$this->model) return false;
$request = function_exists('request') ? request() : null;
$ip = $request ? $request->getRealIp() : '0.0.0.0';
if (!$this->token) {
$this->token = Random::uuid();
Token::set($this->token, self::TOKEN_TYPE, $this->model->id, $this->keepTime);
}
$this->model->startTrans();
try {
$this->model->login_failure = 0;
$this->model->last_login_time = time();
$this->model->last_login_ip = $ip;
$this->model->save();
$this->loginEd = true;
$this->model->commit();
} catch (Throwable $e) {
$this->model->rollback();
if ($this->token) {
Token::delete($this->token);
$this->token = '';
}
$this->setError($e->getMessage());
return false;
}
return true;
}
public function loginFailed(): bool
{
if (!$this->model) return false;
$request = function_exists('request') ? request() : null;
$ip = $request ? $request->getRealIp() : '0.0.0.0';
$this->model->startTrans();
try {
$this->model->login_failure++;
$this->model->last_login_time = time();
$this->model->last_login_ip = $ip;
$this->model->save();
$this->model->commit();
} catch (Throwable $e) {
$this->model->rollback();
}
return $this->reset();
}
public function logout(): bool
{
if (!$this->loginEd) {
$this->setError('You are not logged in');
return false;
}
return $this->reset();
}
public function isLogin(): bool
{
return $this->loginEd;
}
public function getUser(): User
{
return $this->model;
}
public function getToken(): string
{
return $this->token;
}
public function setRefreshToken(int $keepTime = 0): void
{
$this->refreshToken = Random::uuid();
Token::set($this->refreshToken, self::TOKEN_TYPE . '-refresh', $this->model->id, $keepTime);
}
public function getRefreshToken(): string
{
return $this->refreshToken;
}
public function getUserInfo(): array
{
if (!$this->model) return [];
$info = $this->model->toArray();
$info = array_intersect_key($info, array_flip($this->getAllowFields()));
$info['token'] = $this->getToken();
$info['refresh_token'] = $this->getRefreshToken();
return $info;
}
public function getAllowFields(): array
{
return $this->allowFields;
}
public function setAllowFields($fields): void
{
$this->allowFields = $fields;
}
public function setKeepTime(int $keepTime = 0): void
{
$this->keepTime = $keepTime;
}
public function check(string $name, int $uid = 0, string $relation = 'or', string $mode = 'url'): bool
{
return parent::check($name, $uid ?: $this->id, $relation, $mode);
}
public function getRuleList(int $uid = 0): array
{
return parent::getRuleList($uid ?: $this->id);
}
public function getRuleIds(int $uid = 0): array
{
return parent::getRuleIds($uid ?: $this->id);
}
public function getMenus(int $uid = 0): array
{
return parent::getMenus($uid ?: $this->id);
}
public function isSuperUser(): bool
{
return in_array('*', $this->getRuleIds());
}
public function setError(string $error): Auth
{
$this->error = $error;
return $this;
}
public function getError(): string
{
return $this->error ? __($this->error) : '';
}
protected function reset(bool $deleteToken = true): bool
{
if ($deleteToken && $this->token) {
Token::delete($this->token);
}
$this->token = '';
$this->loginEd = false;
$this->model = null;
$this->refreshToken = '';
$this->setError('');
$this->setKeepTime((int)config('buildadmin.user_token_keep_time', 86400));
return true;
}
}