Files
2026-03-18 15:54:43 +08:00

370 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
declare(strict_types=1);
namespace app\admin\library;
use Throwable;
use ba\Random;
use support\think\Db;
use app\admin\model\Admin;
use app\common\facade\Token;
use app\admin\model\AdminGroup;
/**
* 管理员权限类Webman 迁移版)
* @property int $id 管理员ID
* @property string $username 管理员用户名
* @property string $nickname 管理员昵称
* @property string $email 管理员邮箱
* @property string $mobile 管理员手机号
*/
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 = 'admin';
protected bool $loginEd = false;
protected string $error = '';
protected ?Admin $model = null;
protected string $token = '';
protected string $refreshToken = '';
protected int $keepTime = 86400;
protected int $refreshTokenKeepTime = 2592000;
protected array $allowFields = ['id', 'username', 'nickname', 'avatar', 'last_login_time'];
public function __construct(array $config = [])
{
parent::__construct($config);
$this->setKeepTime((int) config('buildadmin.admin_token_keep_time', 86400 * 3));
}
public function __get($name): mixed
{
return $this->model?->$name;
}
/**
* 初始化Webman从 request 获取或创建新实例)
*/
public static function instance(array $options = []): Auth
{
$request = function_exists('request') ? request() : null;
if ($request !== null && isset($request->adminAuth) && $request->adminAuth instanceof Auth) {
return $request->adminAuth;
}
$auth = new static($options);
if ($request !== null) {
$request->adminAuth = $auth;
}
return $auth;
}
public function init(string $token): bool
{
$tokenData = Token::get($token);
if ($tokenData) {
Token::tokenExpirationCheck($tokenData);
$userId = (int) $tokenData['user_id'];
if ($tokenData['type'] === self::TOKEN_TYPE && $userId > 0) {
$this->model = Admin::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 login(string $username, string $password, bool $keep = false): bool
{
$this->model = Admin::where('username', $username)->find();
if (!$this->model) {
$this->setError('Username is incorrect');
return false;
}
if ($this->model->status === 'disable') {
$this->setError('Account disabled');
return false;
}
$adminLoginRetry = config('buildadmin.admin_login_retry');
if ($adminLoginRetry) {
$lastLoginTime = $this->model->getData('last_login_time');
if ($lastLoginTime) {
if ($this->model->login_failure > 0 && time() - $lastLoginTime >= 86400) {
$this->model->login_failure = 0;
$this->model->save();
$this->model = Admin::where('username', $username)->find();
}
if ($this->model->login_failure >= $adminLoginRetry) {
$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.admin_sso')) {
Token::clear(self::TOKEN_TYPE, $this->model->id);
Token::clear(self::TOKEN_TYPE . '-refresh', $this->model->id);
}
if ($keep) {
$this->setRefreshToken($this->refreshTokenKeepTime);
}
if (!$this->loginSuccessful()) {
return false;
}
return true;
}
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 loginSuccessful(): bool
{
if (!$this->model) {
return false;
}
$this->model->startTrans();
try {
$this->model->login_failure = 0;
$this->model->last_login_time = time();
$this->model->last_login_ip = function_exists('request') && request() ? request()->getRealIp() : '';
$this->model->save();
$this->loginEd = true;
if (!$this->token) {
$this->token = Random::uuid();
Token::set($this->token, self::TOKEN_TYPE, $this->model->id, $this->keepTime);
}
$this->model->commit();
} catch (Throwable $e) {
$this->model->rollback();
$this->setError($e->getMessage());
return false;
}
return true;
}
public function loginFailed(): bool
{
if (!$this->model) {
return false;
}
$this->model->startTrans();
try {
$this->model->login_failure++;
$this->model->last_login_time = time();
$this->model->last_login_ip = function_exists('request') && request() ? request()->getRealIp() : '';
$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 getAdmin(): Admin
{
return $this->model;
}
public function getToken(): string
{
return $this->token;
}
public function getRefreshToken(): string
{
return $this->refreshToken;
}
public function getInfo(): array
{
if (!$this->model) {
return [];
}
$info = $this->model->toArray();
$info = array_intersect_key($info, array_flip($this->getAllowFields()));
// 与 ThinkPHP 一致token 为主认证令牌refresh_token 仅 keep=true 时有值
$token = $this->token;
if (!$token && $this->loginEd) {
$token = Random::uuid();
Token::set($token, self::TOKEN_TYPE, $this->model->id, $this->keepTime);
$this->token = $token;
}
$info['token'] = $token ?: '';
$info['refresh_token'] = $this->refreshToken ?: '';
// last_login_time 与 ThinkPHP 一致返回整数时间戳
if (isset($info['last_login_time'])) {
$info['last_login_time'] = (int) $info['last_login_time'];
}
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 getGroups(int $uid = 0): array
{
return parent::getGroups($uid ?: $this->id);
}
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 isSuperAdmin(): bool
{
return in_array('*', $this->getRuleIds());
}
public function getAdminChildGroups(): array
{
$groupIds = Db::name('admin_group_access')
->where('uid', $this->id)
->select();
$children = [];
foreach ($groupIds as $group) {
$this->getGroupChildGroups($group['group_id'], $children);
}
return array_unique($children);
}
public function getGroupChildGroups(int $groupId, array &$children): void
{
$childrenTemp = AdminGroup::where('pid', $groupId)
->where('status', 1)
->select();
foreach ($childrenTemp as $item) {
$children[] = $item['id'];
$this->getGroupChildGroups($item['id'], $children);
}
}
public function getGroupAdmins(array $groups): array
{
return Db::name('admin_group_access')
->where('group_id', 'in', $groups)
->column('uid');
}
public function getAllAuthGroups(string $dataLimit, array $groupQueryWhere = [['status', '=', 1]]): array
{
$rules = $this->getRuleIds();
$allAuthGroups = [];
$groups = AdminGroup::where($groupQueryWhere)->select();
foreach ($groups as $group) {
if ($group['rules'] === '*') {
continue;
}
$groupRules = explode(',', $group['rules']);
$all = true;
foreach ($groupRules as $groupRule) {
if (!in_array($groupRule, $rules)) {
$all = false;
break;
}
}
if ($all) {
if ($dataLimit === 'allAuth' || ($dataLimit === 'allAuthAndOthers' && array_diff($rules, $groupRules))) {
$allAuthGroups[] = $group['id'];
}
}
}
return $allAuthGroups;
}
public function setError($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.admin_token_keep_time', 86400 * 3));
return true;
}
}