diff --git a/README.md b/README.md index e392fa8a3ce3a62474b3c1103d7f2b64e11cac7e..afd4e0c0a589edcbdcf6c7cc8112a05d74c86236 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ Webman官方文档

- ## 开源单用户独立版本快速开发框架 我们致力于提供一个卓越、简约且美观的开源单用户独立应用开发框架。秉持“取之于开源,回馈于开源”的理念,我们希望为开发者节省宝贵时间,让他们能够享受生活的每一刻——无论是休闲放松、自我提升、陪伴家人、健身锻炼,还是邂逅美好。 @@ -33,18 +32,22 @@ ### 基础架构 - 多租户体系:产品订阅、初始数据、独立数据源等企业级功能 - 权限管理:完整的RBAC权限控制系统 + ### 组织架构 - 用户管理:系统操作者配置中心 - 部门架构:树形组织管理,支持数据权限 - 岗位管理:职务体系配置 + ### 系统管理 - 菜单权限:菜单/操作/按钮权限精细控制 - 角色管理:菜单权限分配+数据范围划分 - 字典系统:常用数据标准化维护 + ### 监控审计 - 通知公告:系统消息发布平台 - 操作日志:完整记录正常与异常操作 - 登录审计:包含异常登录监控 + ### 设计哲学 大道至简 —— 我们坚持: @@ -53,39 +56,32 @@ - 架构清晰明了 - 无论是前端还是后端开发,都能享受**"快就完事了"**的编程体验。 - ## 环境需求 为了确保系统的正常运行,请确保您的服务器环境满足以下要求: -- PHP:版本需 >= 8.1,并开启以下扩展: -- mbstring:用于多字节字符串处理。 -- json:支持JSON数据的编码和解码。 -- pdo:提供统一的数据库访问接口。 -- openssl:用于加密和解密操作,保障数据传输安全。 -- redis:支持Redis缓存和数据存储。 -- pcntl:用于多进程控制。 -- Mysql:版本需 >= 5.7,作为系统的主要数据库存储数据。 -- Redis:版本需 >= 4.0,用于缓存数据,提高系统性能。 -- Git:版本需 >= 2.x,方便代码的版本管理和协作开发。 -- Composer:版本需 >= 2.x,用于PHP依赖管理。 - +- **PHP**:版本需 >= 8.1,并开启以下扩展: + - mbstring:用于多字节字符串处理。 + - json:支持JSON数据的编码和解码。 + - pdo:提供统一的数据库访问接口。 + - openssl:用于加密和解密操作,保障数据传输安全。 + - redis:支持Redis缓存和数据存储。 + - pcntl:用于多进程控制。 +- **Mysql**:版本需 >= 5.7,作为系统的主要数据库存储数据。 +- **Redis**:版本需 >= 4.0,用于缓存数据,提高系统性能。 +- **Git**:版本需 >= 2.x,方便代码的版本管理和协作开发。 +- **Composer**:版本需 >= 2.x,用于PHP依赖管理。 ## 体验地址 - [体验地址](https://admin.madong.tech) https://admin.madong.tech - 账号:admin - 密码:123456 -期待您的使用和反馈,我们将不断优化和完善系统,为您提供更优质的服务。 - +期待您的使用和反馈,我们将不断优化和完善系统,为您提供更优质的服务。 ## 官方社区 欢迎加入我们的官方社区交流互动 - ## 共同交流 - - ## 加入我们 -欢迎加入技术交流群,与核心开发者直接沟通(扫码添加作者微信进群),共同打造更强大的开源企业级平台! +欢迎加入技术交流群,与核心开发者直接沟通(扫码添加作者微信进群),共同打造更强大的开源企业级平台! \ No newline at end of file diff --git a/server/app/admin/controller/LoginController.php b/server/app/admin/controller/LoginController.php index 68f77afbcfbeecfb326c561c5994a8b44f05f194..9b8c68b8e7b0dc5fe9242de02e8c4b60eef7e345 100644 --- a/server/app/admin/controller/LoginController.php +++ b/server/app/admin/controller/LoginController.php @@ -18,6 +18,8 @@ use core\exception\handler\AdminException; use core\jwt\JwtToken; use core\utils\Json; use core\captcha\Captcha; +use core\utils\RSAService; +use core\uuid\UUIDGenerator; use support\Container; use support\Request; @@ -58,13 +60,12 @@ class LoginController extends Crud { try { // 生成密钥对 - $cache = Container::make(CacheService::class,[]); - // 使用 uniqid 增加唯一性 - $keyId = bin2hex(random_bytes(8)) . uniqid(); - $keys = $this->service->generateRSAKeys(); + $cache = Container::make(CacheService::class, []); + $keyId = md5(UUIDGenerator::generate()); + $keys = RSAService::generateKeys(); // 存储私钥到缓存,用于解密密码 - $cache->set("rsa_private_key:$keyId", $keys['private'], 60); // 5分钟过期 - return Json::success('ok', ['flag' => config('core.captcha.app.enable', false),'key_id'=>$keyId,'public_key'=>$keys['public']]); + $cache->set("rsa_private_key_$keyId", $keys['private'], 60); // 5分钟过期 + return Json::success('ok', ['flag' => config('core.captcha.app.enable', false), 'key_id' => $keyId, 'public_key' => $keys['public']]); } catch (\Throwable $e) { return Json::fail($e->getMessage()); } @@ -127,12 +128,10 @@ class LoginController extends Crud $type = $request->input('type', 'admin'); $grantType = $request->input('grant_type', 'default');//refresh_token sms default 可以自行定义拓展登录方式 $keyId = $request->input('key_id', '');//获取公钥Id + /** @var SysAdminService $service */ $service = Container::make(SysAdminService::class); $captcha = new Captcha(); -// if (config('tenant.enabled') && empty($tenantId)) { -// throw new AdminException('请选择数据源!'); -// } if (config('core.captcha.app.enable') && $grantType === 'default') { if (!$captcha->check($uuid, $code)) { @@ -150,7 +149,8 @@ class LoginController extends Crud } $username = $info->getData('user_name'); } - $data = $service->login($username, $password, $type, $grantType, ['keyId'=> $keyId ?? '']); + + $data = $service->login($username, $password, $type, $grantType, ['key_id' => $keyId ?? '']); return Json::success('ok', $data); } catch (\Throwable $e) { return Json::fail($e->getMessage()); diff --git a/server/app/common/dao/system/SysAdminDao.php b/server/app/common/dao/system/SysAdminDao.php index 89ce32b25810d7a772fd02b01eded53e84fa09cc..51d27def1dd9005b47e118378d96cff3eb346bc2 100644 --- a/server/app/common/dao/system/SysAdminDao.php +++ b/server/app/common/dao/system/SysAdminDao.php @@ -15,7 +15,6 @@ namespace app\common\dao\system; use app\common\model\system\SysAdmin; use InvalidArgumentException; use core\abstract\BaseDao; -use core\context\TenantContext; class SysAdminDao extends BaseDao { @@ -28,11 +27,11 @@ class SysAdminDao extends BaseDao /** * 用户详情 * - * @param $id - * @param array|null $field - * @param array|null $with - * @param string $order - * @param array|null $withoutScopes + * @param $id + * @param array|null $field + * @param array|null $with + * @param string $order + * @param array|null $withoutScopes * * @return SysAdmin|null * @throws \Exception @@ -90,17 +89,53 @@ class SysAdminDao extends BaseDao */ public function getList(array $where, string $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): array { -// $where['enabled'] = 1;//显示禁用用户列表 + // 初始化部门ID + $deptId = null; + + // 遍历$where数组,查找并移除dept_id条件 + foreach ($where as $index => $condition) { + // 条件可能是索引数组,第一个元素是字段名 + if (is_array($condition) && count($condition) >= 2) { + $fieldName = $condition[0]; + // 如果字段名是'dept_id' + if ($fieldName === 'dept_id') { + // 根据条件数组的长度判断操作符和值的位置 + if (count($condition) === 2) { + // 条件格式为 ['dept_id', 值] + $deptId = $condition[1]; + } elseif (count($condition) === 3) { + // 条件格式为 ['dept_id', '=', 值] + $deptId = $condition[2]; + } + // 移除这个条件 + unset($where[$index]); + // 因为我们只处理一个dept_id条件,所以找到后跳出循环 + break; + } + } + } + + // 重新索引数组,防止父类处理时出错 + $where = array_values($where); + if (empty($with)) { $with = ['depts', 'posts', 'casbin.roles']; } + $query = parent::selectModel($where, $field, $page, $limit, $order, $with, $search, $withoutScopes); + + // 如果存在deptId条件,则添加关联条件 + if (!is_null($deptId)) { + $query->whereHas('depts', function ($q) use ($deptId) { + $q->where('id', $deptId); // 假设部门模型的主键是id + }); + } + $total = $query->count(); $items = $query->get()->makeHidden(['password', 'backend_setting']); return [$total, $items]; } - /** * 获取用户列表-角色id * @@ -120,7 +155,7 @@ class SysAdminDao extends BaseDao } $where['enabled'] = 1;//有效用户 $where['is_super'] = 0;//非顶级管理员 - $query = $this->getModel()->with(['roles']) + $query = $this->getModel()->with(['roles']) ->whereHas('roles', function ($query) use ($roleId) { $query->where('id', $roleId); }); @@ -201,7 +236,7 @@ class SysAdminDao extends BaseDao { $result = $this->getModel() ->where('id', $id) - ->with(['depts', 'posts', 'casbin.roles','roles']) + ->with(['depts', 'posts', 'casbin.roles', 'roles']) ->first() ->makeHidden(['password', 'backend_setting']); diff --git a/server/app/common/dao/system/SysCrontabDao.php b/server/app/common/dao/system/SysCrontabDao.php index dc2c7e7a9c84cec8e579c83782a6f2369f0504d4..ba3e314035497a396397ccf830cb1c6d214152eb 100644 --- a/server/app/common/dao/system/SysCrontabDao.php +++ b/server/app/common/dao/system/SysCrontabDao.php @@ -38,7 +38,7 @@ class SysCrontabDao extends BaseDao * @return \Illuminate\Database\Eloquent\Collection|null * @throws \Exception */ - public function selectList(array $where, string $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false,?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection + public function selectList(array $where, string|array $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection { $result = parent::selectList($where, $field, $page, $limit, $order, [], $search,$withoutScopes); diff --git a/server/app/common/dao/system/SysDeptDao.php b/server/app/common/dao/system/SysDeptDao.php index 875fe6677c619e2ee1652772d11a0247e61ea94d..35264a7d5afeb31ae4ca5f3cde95761fd14bf552 100644 --- a/server/app/common/dao/system/SysDeptDao.php +++ b/server/app/common/dao/system/SysDeptDao.php @@ -26,7 +26,7 @@ class SysDeptDao extends BaseDao - public function selectList(array $where, string $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection + public function selectList(array $where, string|array $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection { return parent::selectList($where, $field, $page, $limit, $order, ['leader'], $search, $withoutScopes); } diff --git a/server/app/common/dao/system/SysMenuDao.php b/server/app/common/dao/system/SysMenuDao.php index 541b2c0464471d7abc273a6ba828851df1a593a9..2b7a01c857fd1107797f3d4402c538bf7fb7e8de 100644 --- a/server/app/common/dao/system/SysMenuDao.php +++ b/server/app/common/dao/system/SysMenuDao.php @@ -29,7 +29,7 @@ class SysMenuDao extends BaseDao return SysMenu::class; } - public function selectList(array $where, string $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection + public function selectList(array $where, string|array $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection { $order='sort'; return parent::selectList($where, $field, $page, $limit, $order, [], $search, $withoutScopes); diff --git a/server/app/common/dao/system/SysMessageDao.php b/server/app/common/dao/system/SysMessageDao.php index 448c53ad7b30a9d767c37771dae58372d04d7a38..5921eab183517619ed63cca38b354b4b503a59b1 100644 --- a/server/app/common/dao/system/SysMessageDao.php +++ b/server/app/common/dao/system/SysMessageDao.php @@ -28,8 +28,7 @@ class SysMessageDao extends BaseDao return parent::get($id, $field, ['sender'], $order, $withoutScopes); } - - public function selectList(array $where, string $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection + public function selectList(array $where, string|array $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection { return parent::selectList($where, $field, $page, $limit, 'created_at desc', ['sender'], $search, $withoutScopes); } diff --git a/server/app/common/dao/system/SysOperateLogDao.php b/server/app/common/dao/system/SysOperateLogDao.php index 7e4bc9e22d27c57eb79c48a3a35f016ca185388e..01793f34bdaae0e391655c5f4a170e8485e0eb35 100644 --- a/server/app/common/dao/system/SysOperateLogDao.php +++ b/server/app/common/dao/system/SysOperateLogDao.php @@ -32,19 +32,19 @@ class SysOperateLogDao extends BaseDao /** * 操作日志列表 * - * @param array $where - * @param string $field - * @param int $page - * @param int $limit - * @param string $order - * @param array $with - * @param bool $search - * @param array|null $withoutScopes + * @param array $where + * @param string|array $field + * @param int $page + * @param int $limit + * @param string $order + * @param array $with + * @param bool $search + * @param array|null $withoutScopes * * @return \Illuminate\Database\Eloquent\Collection|null * @throws \Exception */ - public function selectList(array $where, string $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection + public function selectList(array $where, string|array $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection { //注意不要输出result 要不然深度递归会卡死 return parent::selectList($where, $field, $page, $limit, $order, $with, $search, $withoutScopes)->makeHidden(['result']); diff --git a/server/app/common/dao/system/SysRecycleBinDao.php b/server/app/common/dao/system/SysRecycleBinDao.php index 83177550dcdafd184a9009e0ff1c402f586a84b6..bc244b9f7e5eb154396ae809fa92b9b99e9faabc 100644 --- a/server/app/common/dao/system/SysRecycleBinDao.php +++ b/server/app/common/dao/system/SysRecycleBinDao.php @@ -29,7 +29,7 @@ class SysRecycleBinDao extends BaseDao return SysRecycleBin::class; } - public function selectList(array $where, string $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection + public function selectList(array $where, string|array $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection { return parent::selectList($where, $field, $page, $limit, $order, ['operate'], $search, $withoutScopes); } diff --git a/server/app/common/dao/system/SysRoleDao.php b/server/app/common/dao/system/SysRoleDao.php index 3aca30db14135ae93d4ceb00ea0c97d3d4abcd64..7cc69d3ae8019d525c344af22032b93b18d9a403 100644 --- a/server/app/common/dao/system/SysRoleDao.php +++ b/server/app/common/dao/system/SysRoleDao.php @@ -146,7 +146,7 @@ class SysRoleDao extends BaseDao * @return \Illuminate\Database\Eloquent\Collection|null * @throws \Exception */ - public function selectList(array $where, string $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection + public function selectList(array $where, string|array $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection { $with = ['casbin']; return parent::selectList($where, $field, $page, $limit, $order, $with, $search, $withoutScopes); diff --git a/server/app/common/dao/system/SysUploadDao.php b/server/app/common/dao/system/SysUploadDao.php index 3b2eb17f41b1119130c19e26bf383b27111bfa55..bb799ccbf5c9b90a1e4179ee15c8a882ee1ce39d 100644 --- a/server/app/common/dao/system/SysUploadDao.php +++ b/server/app/common/dao/system/SysUploadDao.php @@ -29,7 +29,7 @@ class SysUploadDao extends BaseDao return SysUpload::class; } - public function selectList(array $where, string $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection + public function selectList(array $where, string|array $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection { //重写追加两个关联模型 return parent::selectList($where, $field, $page, $limit, $order, ['createds', 'updateds'], $search, $withoutScopes); diff --git a/server/app/common/services/system/SysAdminService.php b/server/app/common/services/system/SysAdminService.php index 81a4e71e8a3933259b870298f31aede7e40d65e2..73c69343d38638988c22d8df5d5e7254b88551b3 100644 --- a/server/app/common/services/system/SysAdminService.php +++ b/server/app/common/services/system/SysAdminService.php @@ -20,6 +20,8 @@ use core\casbin\Permission; use core\enum\system\PolicyPrefix; use core\exception\handler\AdminException; use core\jwt\JwtToken; +use core\utils\RSAService; +use phpseclib3\Crypt\RSA; use support\Container; use Webman\Event\Event; @@ -236,20 +238,20 @@ class SysAdminService extends BaseService /** * 用户登录 * - * @param string $username - * @param string $password - * @param string $type - * @param string $grantType - * @param string|int $tenantId + * @param string $username + * @param string $password + * @param string $type + * @param string $grantType + * @param array $params * * @return array - * @throws \Exception + * @throws \core\exception\handler\AdminException */ public function login(string $username, string $password = '', string $type = 'admin', string $grantType = 'default', array $params = []): array { $adminInfo = $this->getAdminByName($username); $this->validateAdminStatus($adminInfo); - $decryptedPassword = $this->validateRsaKeys($params['keyId'], $password); + $decryptedPassword = $this->validateRsaKeys($params['key_id'], $password); $this->validatePassword($adminInfo, $decryptedPassword, $grantType); [$userInfo, $token] = $this->generateTokenData($adminInfo, $type); $this->emitLoginSuccessEvent(array_merge($userInfo, $token), $tenant?->id ?? null); @@ -294,7 +296,6 @@ class SysAdminService extends BaseService } } - /** * token生成 * @@ -483,28 +484,25 @@ class SysAdminService extends BaseService preg_match($pattern, $url, $matches); return $matches[1] ?? ''; } + /** * 校验密钥 + * * @param $keyId * @param $encryptedPassword + * * @return string * @throws AdminException */ private function validateRsaKeys($keyId, $encryptedPassword): string { - $cache = Container::make(CacheService::class,[]); - $privateKey = $cache->get("rsa_private_key:$keyId"); + $cache = Container::make(CacheService::class, []); + $privateKey = $cache->get("rsa_private_key_$keyId"); if (!$privateKey) { throw new AdminException('私钥不存在或已过期,请刷新页面重试'); } - $privateKeyResource = openssl_pkey_get_private($privateKey); - $decrypted = ''; - $encryptedData = base64_decode($encryptedPassword); - if (openssl_private_decrypt($encryptedData, $decrypted, $privateKeyResource)) { - $cache->delete("rsa_private_key:$keyId"); // 删除私钥,防止泄露 - return $decrypted; - } else { - throw new AdminException('解密失败!'); - } + return RSAService::decrypt($encryptedPassword, $privateKey); } } + + diff --git a/server/composer.json b/server/composer.json index 32ca147bcf10bfe254a5b96fba6133ec3351124b..261fca2fc14a8748c834f0dd4fb44ba95c2c7abf 100644 --- a/server/composer.json +++ b/server/composer.json @@ -73,7 +73,8 @@ "topthink/think-template": "^3.0", "ext-gd": "*", "php-di/php-di": "^7.0", - "casbin/casbin": "^4.0" + "casbin/casbin": "^4.0", + "phpseclib/phpseclib": "^3.0" }, "suggest": { "ext-event": "For better performance. " diff --git a/server/composer.lock b/server/composer.lock index 7f41469a9b8c25fba963a3da71d369ba996d427e..9616d999963e71d1693e50e3ef75be43116b09d4 100644 --- a/server/composer.lock +++ b/server/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f817c0f2a5ba81575320f7454197f6df", + "content-hash": "57d030f5acb60a4099e32eebfb29f6be", "packages": [ { "name": "aliyuncs/oss-sdk-php", @@ -3594,6 +3594,123 @@ ], "time": "2025-03-10T09:33:39+00:00" }, + { + "name": "paragonie/constant_time_encoding", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "df1e7fde177501eee2037dd159cf04f5f301a512" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/df1e7fde177501eee2037dd159cf04f5f301a512", + "reference": "df1e7fde177501eee2037dd159cf04f5f301a512", + "shasum": "" + }, + "require": { + "php": "^8" + }, + "require-dev": { + "phpunit/phpunit": "^9", + "vimeo/psalm": "^4|^5" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "source": "https://github.com/paragonie/constant_time_encoding" + }, + "time": "2024-05-08T12:36:18+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" + }, { "name": "php-di/invoker", "version": "2.3.6", @@ -3984,6 +4101,116 @@ ], "time": "2024-07-20T21:41:07+00:00" }, + { + "name": "phpseclib/phpseclib", + "version": "3.0.46", + "source": { + "type": "git", + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6", + "reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6", + "shasum": "" + }, + "require": { + "paragonie/constant_time_encoding": "^1|^2|^3", + "paragonie/random_compat": "^1.4|^2.0|^9.99.99", + "php": ">=5.6.1" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "suggest": { + "ext-dom": "Install the DOM extension to load XML formatted public keys.", + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", + "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", + "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." + }, + "type": "library", + "autoload": { + "files": [ + "phpseclib/bootstrap.php" + ], + "psr-4": { + "phpseclib3\\": "phpseclib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "role": "Lead Developer" + }, + { + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" + }, + { + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" + }, + { + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "role": "Developer" + } + ], + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "http://phpseclib.sourceforge.net", + "keywords": [ + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" + ], + "support": { + "issues": "https://github.com/phpseclib/phpseclib/issues", + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.46" + }, + "funding": [ + { + "url": "https://github.com/terrafrost", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpseclib", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", + "type": "tidelift" + } + ], + "time": "2025-06-26T16:29:55+00:00" + }, { "name": "psr/cache", "version": "3.0.0", @@ -8177,7 +8404,7 @@ "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, "platform": { @@ -8186,6 +8413,6 @@ "ext-pdo": "*", "ext-gd": "*" }, - "platform-dev": {}, + "platform-dev": [], "plugin-api-version": "2.6.0" } diff --git a/server/core/abstract/BaseDao.php b/server/core/abstract/BaseDao.php index f72c5e8a39df18a46fdab2868f5c499aae2e0e7d..c45cee2ccd270035ff2bf53039e5ee39ed45d069 100644 --- a/server/core/abstract/BaseDao.php +++ b/server/core/abstract/BaseDao.php @@ -388,15 +388,15 @@ abstract class BaseDao * 获取一条数据 * * @param $id - * @param string|array|null $field + * @param array|null $field * @param array|null $with - * @param string $order + * @param string $order * @param array|null $withoutScopes * * @return Model|null - * @throws Exception + * @throws \Exception */ - public function get($id, string|array $field = null, ?array $with = [], string $order = '', ?array $withoutScopes = null): ?Model + public function get($id, ?array $field = null, ?array $with = [], string $order = '', ?array $withoutScopes = null): ?Model { $where = is_array($id) ? $id : [$this->getPk() => $id]; $query = $this->getModel()->query(); diff --git a/server/core/utils/RSAService.php b/server/core/utils/RSAService.php new file mode 100644 index 0000000000000000000000000000000000000000..9bbb14ed845a30c7b3ed03eab67fdf50fc9b6b83 --- /dev/null +++ b/server/core/utils/RSAService.php @@ -0,0 +1,132 @@ +toString('PKCS1'); + $publicPem = $privateKey->getPublicKey()->toString('PKCS8'); + + return [ + 'private' => $privatePem, + 'public' => $publicPem, + ]; + } + + /** + * 签名 + * + * @param string $data + * @param string $privatePem + * + * @return string + */ + public static function sign(string $data, string $privatePem): string + { + $privateKey = PublicKeyLoader::loadPrivateKey($privatePem) + ->withHash('sha256') + ->withPadding(RSA::SIGNATURE_PKCS1); + + return $privateKey->sign($data); + } + + /** + * 验证签名 + * + * @param string $data + * @param string $signature + * @param string $publicPem + * + * @return bool + */ + public static function verify(string $data, string $signature, string $publicPem): bool + { + $publicKey = PublicKeyLoader::load($publicPem) + ->withHash('sha256') + ->withPadding(RSA::SIGNATURE_PKCS1); + + return $publicKey->verify($data, $signature); + } + + /** + * 公钥加密 + * + * @param string $data + * @param string $publicPem + * + * @return string + */ + public static function encrypt(string $data, string $publicPem): string + { + $publicKey = PublicKeyLoader::load($publicPem)->withPadding(RSA::ENCRYPTION_PKCS1); + return base64_encode($publicKey->encrypt($data)); + } + + /** + * 私钥解密 + * + * @param string $cipherBase64 + * @param string $privatePem + * + * @return string + */ + public static function decrypt(string $cipherBase64, string $privatePem): string + { + $privateKey = PublicKeyLoader::loadPrivateKey($privatePem) + ->withPadding(RSA::ENCRYPTION_PKCS1); + + $cipher = base64_decode($cipherBase64); + return $privateKey->decrypt($cipher); + } + + + /** + * 公钥掩码显示 + * + * @param string $pem + * @param int $keep + * + * @return string + */ + public static function maskKey(string $pem, int $keep = 20): string + { + $clean = str_replace( + ["\r", "\n", "-----BEGIN PUBLIC KEY-----", "-----END PUBLIC KEY-----"], + '', + $pem + ); + $len = strlen($clean); + return substr($clean, 0, $keep) . '...' . substr($clean, -$keep); + } +} diff --git a/server/core/uuid/UUIDGenerator.php b/server/core/uuid/UUIDGenerator.php index d77f91029fb0ca30b513ae866bf04e1a0cefdc1c..5f1c314ad501d6be84ad3011b9e26fcc7007a6c1 100644 --- a/server/core/uuid/UUIDGenerator.php +++ b/server/core/uuid/UUIDGenerator.php @@ -29,7 +29,7 @@ class UUIDGenerator * @return string|null * @throws \Exception */ - public static function generate(string $format = 'uuid', int $length = 36) + public static function generate(string $format = 'uuid', int $length = 36): ?string { if ($format === 'uuid') { return self::generateUUID(); diff --git a/server/plugin/install/app/functions.php b/server/plugin/install/app/functions.php index f1a4eefc69b8c03173fdf7f08b0106c24680823b..3986b9ab29c8dcf9cc30f93b42ec0bbaa01e2f06 100644 --- a/server/plugin/install/app/functions.php +++ b/server/plugin/install/app/functions.php @@ -130,35 +130,35 @@ function generateEnvTemplate(): string # Database Configuration DB_CONNECTION=mysql - DB_HOST = ~db_host~ - DB_PORT = ~db_port~ - DB_DATABASE= ~db_name~ - DB_USERNAME= ~db_user~ - DB_PASSWORD= ~db_pwd~ - DB_PREFIX = ~db_prefix~ + DB_HOST=~db_host~ + DB_PORT=~db_port~ + DB_DATABASE=~db_name~ + DB_USERNAME=~db_user~ + DB_PASSWORD=~db_pwd~ + DB_PREFIX=~db_prefix~ # Redis Configuration - REDIS_HOST= ~redis_host~ - REDIS_PORT= ~redis_port~ - REDIS_PASSWORD= ~redis_pwd~ + REDIS_HOST=~redis_host~ + REDIS_PORT=~redis_port~ + REDIS_PASSWORD=~redis_pwd~ REDIS_DB=0 # Queue Redis Configuration - QUEUE_REDIS_HOST= ~redis_host~ - QUEUE_REDIS_PORT= ~redis_port~ - QUEUE_REDIS_PASSWORD= ~redis_pwd~ + QUEUE_REDIS_HOST=~redis_host~ + QUEUE_REDIS_PORT=~redis_port~ + QUEUE_REDIS_PASSWORD=~redis_pwd~ QUEUE_REDIS_DB=0 QUEUE_REDIS_PREFIX=queue # Cache Configuration - CACHE_CUSTOM_REDIS_HOST= ~redis_host~ - CACHE_CUSTOM_REDIS_PORT= ~redis_port~ - CACHE_CUSTOM_REDIS_PASSWORD= ~redis_pwd~ + CACHE_CUSTOM_REDIS_HOST=~redis_host~ + CACHE_CUSTOM_REDIS_PORT=~redis_port~ + CACHE_CUSTOM_REDIS_PASSWORD=~redis_pwd~ CACHE_CUSTOM_REDIS_DB=0 CACHE_CUSTOM_REDIS_PREFIX=cache_custom # Feature Toggles - APP_TASK_ENABLED=false # Timer switch + APP_TASK_ENABLED=false # Timer switch APP_TENANT_ENABLED=true # Tenant mode switch APP_TENANT_AUTO_SELECT_FIRST=false # Tenant auto CAPTCHA_ENABLED=false # Captcha switch diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 510e4777e6aeda26f3c7b68a311c52c14b49c685..9d82a9b4ac7a457f624e9a60d92c842f70256392 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -2317,6 +2317,9 @@ packages: js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + jsencrypt@3.5.4: + resolution: {integrity: sha512-kNjfYEMNASxrDGsmcSQh/rUTmcoRfSUkxnAz+MMywM8jtGu+fFEZ3nJjHM58zscVnwR0fYmG9sGkTDjqUdpiwA==} + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -6113,6 +6116,8 @@ snapshots: js-tokens@9.0.1: {} + jsencrypt@3.5.4: {} + jsesc@3.1.0: {} json5@2.2.3: {} diff --git a/web/src/components/common/effects/layouts/widgets/preferences/preferences-drawer.vue b/web/src/components/common/effects/layouts/widgets/preferences/preferences-drawer.vue index e263d30c83bf6c9c781715feebf6435d7cacd14e..ec0b53239df2d83eea6cb03a27dc2cd5cf15731b 100644 --- a/web/src/components/common/effects/layouts/widgets/preferences/preferences-drawer.vue +++ b/web/src/components/common/effects/layouts/widgets/preferences/preferences-drawer.vue @@ -58,6 +58,8 @@ import { import { SystemUserApi } from '#/api/system/user'; import { SaveOutlined } from '@ant-design/icons-vue'; +import { message as antMessage } from 'ant-design-vue'; + const emit = defineEmits<{ clearPreferencesAndLogout: [] }>(); const message = globalShareState.getMessage(); @@ -225,7 +227,12 @@ async function handleCopy() { async function handleUserPreferences(){ const {theme}= preferences; const api=new SystemUserApi(); - await api.preferences({theme}); + try { + await api.preferences({theme}); + antMessage.success($t('preferences.savePreferencesSuccess')); + } catch (error) { + antMessage.error($t('preferences.savePreferencesFailed')); + } } diff --git a/web/src/locale/effects/langs/en-US/preferences.json b/web/src/locale/effects/langs/en-US/preferences.json index db83a97a446fc3bac024c9e844a07a17660bcfa4..e31364eda87bddac35349f62579bb1ac3fdc92f8 100644 --- a/web/src/locale/effects/langs/en-US/preferences.json +++ b/web/src/locale/effects/langs/en-US/preferences.json @@ -29,6 +29,8 @@ "plain": "Plain", "rounded": "Rounded", "save":"Save", + "savePreferencesSuccess": "Preferences saved successfully", + "savePreferencesFailed": "Failed to save preferences", "copyPreferences": "Copy Preferences", "copyPreferencesSuccessTitle": "Copy successful", "copyPreferencesSuccess": "Copy successful, please override in `src/preferences.ts` under app", diff --git a/web/src/locale/effects/langs/zh-CN/preferences.json b/web/src/locale/effects/langs/zh-CN/preferences.json index 21aa4a7be5402bc7a55dc5a8a7cab4414d17c200..ae437f47c014f9951af25985b8ef4cea8c641ee1 100644 --- a/web/src/locale/effects/langs/zh-CN/preferences.json +++ b/web/src/locale/effects/langs/zh-CN/preferences.json @@ -30,6 +30,8 @@ "rounded": "圆润", "copyPreferences": "复制偏好设置", "save":"保存", + "savePreferencesSuccess": "偏好设置保存成功", + "savePreferencesFailed": "偏好设置保存失败", "copyPreferencesSuccessTitle": "复制成功", "copyPreferencesSuccess": "复制成功,请在 app 下的 `src/preferences.ts`内进行覆盖", "clearAndLogout": "清空缓存 & 退出登录",