From 8a6f2943625f169913c40b1618a7d2e7058bb703 Mon Sep 17 00:00:00 2001 From: M1Screw <14369594+M1Screw@users.noreply.github.com> Date: Fri, 22 Sep 2023 05:54:17 +0800 Subject: [PATCH 1/3] feat: collect invalid user login --- composer.lock | 114 +++++++++++----------- src/Controllers/AuthController.php | 5 +- src/Controllers/PasswordController.php | 1 + src/Controllers/SubController.php | 4 +- src/Controllers/UserController.php | 2 + src/Controllers/WebAPI/FuncController.php | 2 +- src/Controllers/WebAPI/NodeController.php | 2 +- src/Controllers/WebAPI/UserController.php | 2 +- src/Models/LoginIp.php | 21 +++- src/Models/Model.php | 1 + src/Models/Payback.php | 32 +++--- src/Models/SubscribeLog.php | 18 ++-- src/Models/User.php | 3 + src/Services/Gateway/AbstractPayment.php | 2 +- src/Utils/Hash.php | 4 + src/Utils/ResponseHelper.php | 16 ++- 16 files changed, 137 insertions(+), 92 deletions(-) diff --git a/composer.lock b/composer.lock index d81b39cf5c..c9a3ef1712 100644 --- a/composer.lock +++ b/composer.lock @@ -123,16 +123,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.281.8", + "version": "3.281.11", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "eb349b9f31502a05c70362f57913b9fed6b65b1f" + "reference": "9d466efae67d5016ed132fd4ffa1566a7d4cab98" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/eb349b9f31502a05c70362f57913b9fed6b65b1f", - "reference": "eb349b9f31502a05c70362f57913b9fed6b65b1f", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/9d466efae67d5016ed132fd4ffa1566a7d4cab98", + "reference": "9d466efae67d5016ed132fd4ffa1566a7d4cab98", "shasum": "" }, "require": { @@ -212,9 +212,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.281.8" + "source": "https://github.com/aws/aws-sdk-php/tree/3.281.11" }, - "time": "2023-09-15T18:34:59+00:00" + "time": "2023-09-20T19:16:24+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1237,16 +1237,16 @@ }, { "name": "illuminate/collections", - "version": "v10.23.1", + "version": "v10.24.0", "source": { "type": "git", "url": "https://github.com/illuminate/collections.git", - "reference": "72c3cc6d44416db499d2ad11b8b27ae22e60a661" + "reference": "939a975daa8a5f77974ffa6a24067f5e947683f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/collections/zipball/72c3cc6d44416db499d2ad11b8b27ae22e60a661", - "reference": "72c3cc6d44416db499d2ad11b8b27ae22e60a661", + "url": "https://api.github.com/repos/illuminate/collections/zipball/939a975daa8a5f77974ffa6a24067f5e947683f4", + "reference": "939a975daa8a5f77974ffa6a24067f5e947683f4", "shasum": "" }, "require": { @@ -1288,11 +1288,11 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-09-07T14:13:46+00:00" + "time": "2023-09-18T18:32:31+00:00" }, { "name": "illuminate/conditionable", - "version": "v10.23.1", + "version": "v10.24.0", "source": { "type": "git", "url": "https://github.com/illuminate/conditionable.git", @@ -1338,7 +1338,7 @@ }, { "name": "illuminate/container", - "version": "v10.23.1", + "version": "v10.24.0", "source": { "type": "git", "url": "https://github.com/illuminate/container.git", @@ -1389,7 +1389,7 @@ }, { "name": "illuminate/contracts", - "version": "v10.23.1", + "version": "v10.24.0", "source": { "type": "git", "url": "https://github.com/illuminate/contracts.git", @@ -1437,16 +1437,16 @@ }, { "name": "illuminate/database", - "version": "v10.23.1", + "version": "v10.24.0", "source": { "type": "git", "url": "https://github.com/illuminate/database.git", - "reference": "41511347b8b74ef80b4e01d2b64d2b4a4263658a" + "reference": "dbf15dca652e25f2bd9607e78dc527464edbec1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/database/zipball/41511347b8b74ef80b4e01d2b64d2b4a4263658a", - "reference": "41511347b8b74ef80b4e01d2b64d2b4a4263658a", + "url": "https://api.github.com/repos/illuminate/database/zipball/dbf15dca652e25f2bd9607e78dc527464edbec1d", + "reference": "dbf15dca652e25f2bd9607e78dc527464edbec1d", "shasum": "" }, "require": { @@ -1502,11 +1502,11 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-09-04T14:22:40+00:00" + "time": "2023-09-19T14:11:01+00:00" }, { "name": "illuminate/macroable", - "version": "v10.23.1", + "version": "v10.24.0", "source": { "type": "git", "url": "https://github.com/illuminate/macroable.git", @@ -1552,7 +1552,7 @@ }, { "name": "illuminate/pagination", - "version": "v10.23.1", + "version": "v10.24.0", "source": { "type": "git", "url": "https://github.com/illuminate/pagination.git", @@ -1602,16 +1602,16 @@ }, { "name": "illuminate/support", - "version": "v10.23.1", + "version": "v10.24.0", "source": { "type": "git", "url": "https://github.com/illuminate/support.git", - "reference": "c21d327c2392eccd93748a2c9f1069a283a62d37" + "reference": "f3ac48a059e3f1a0cb72c926f83a77af77d7a7f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/support/zipball/c21d327c2392eccd93748a2c9f1069a283a62d37", - "reference": "c21d327c2392eccd93748a2c9f1069a283a62d37", + "url": "https://api.github.com/repos/illuminate/support/zipball/f3ac48a059e3f1a0cb72c926f83a77af77d7a7f3", + "reference": "f3ac48a059e3f1a0cb72c926f83a77af77d7a7f3", "shasum": "" }, "require": { @@ -1669,7 +1669,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-09-12T18:56:44+00:00" + "time": "2023-09-19T14:13:21+00:00" }, { "name": "irazasyed/telegram-bot-sdk", @@ -6319,16 +6319,16 @@ }, { "name": "tronovav/geoip2-update", - "version": "v2.2.2", + "version": "v2.2.4", "source": { "type": "git", "url": "https://github.com/tronovav/geoip2-update.git", - "reference": "35ea93da5784740ffe308be0888131efa94a0e0b" + "reference": "60dc374c21e97ca16cadfbb418a4bbd6045b581e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tronovav/geoip2-update/zipball/35ea93da5784740ffe308be0888131efa94a0e0b", - "reference": "35ea93da5784740ffe308be0888131efa94a0e0b", + "url": "https://api.github.com/repos/tronovav/geoip2-update/zipball/60dc374c21e97ca16cadfbb418a4bbd6045b581e", + "reference": "60dc374c21e97ca16cadfbb418a4bbd6045b581e", "shasum": "" }, "require": { @@ -6366,9 +6366,9 @@ "support": { "email": "newtronov@gmail.com", "issues": "https://github.com/tronovav/geoip2-update/issues", - "source": "https://github.com/tronovav/geoip2-update/tree/v2.2.2" + "source": "https://github.com/tronovav/geoip2-update/tree/v2.2.4" }, - "time": "2023-09-15T22:25:45+00:00" + "time": "2023-09-21T16:52:07+00:00" }, { "name": "vectorface/googleauthenticator", @@ -7746,16 +7746,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.24.0", + "version": "1.24.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "3510b0a6274cc42f7219367cb3abfc123ffa09d6" + "reference": "9f854d275c2dbf84915a5c0ec9a2d17d2cd86b01" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/3510b0a6274cc42f7219367cb3abfc123ffa09d6", - "reference": "3510b0a6274cc42f7219367cb3abfc123ffa09d6", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9f854d275c2dbf84915a5c0ec9a2d17d2cd86b01", + "reference": "9f854d275c2dbf84915a5c0ec9a2d17d2cd86b01", "shasum": "" }, "require": { @@ -7787,22 +7787,22 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.1" }, - "time": "2023-09-07T20:46:32+00:00" + "time": "2023-09-18T12:18:02+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "10.1.5", + "version": "10.1.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "1df504e42a88044c27a90136910f0b3fe9e91939" + "reference": "56f33548fe522c8d82da7ff3824b42829d324364" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/1df504e42a88044c27a90136910f0b3fe9e91939", - "reference": "1df504e42a88044c27a90136910f0b3fe9e91939", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/56f33548fe522c8d82da7ff3824b42829d324364", + "reference": "56f33548fe522c8d82da7ff3824b42829d324364", "shasum": "" }, "require": { @@ -7859,7 +7859,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.5" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.6" }, "funding": [ { @@ -7867,7 +7867,7 @@ "type": "github" } ], - "time": "2023-09-12T14:37:22+00:00" + "time": "2023-09-19T04:59:03+00:00" }, { "name": "phpunit/php-file-iterator", @@ -8114,16 +8114,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.3.4", + "version": "10.3.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "b8d59476f19115c9774b3b447f78131781c6c32b" + "reference": "747c3b2038f1139e3dcd9886a3f5a948648b7503" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b8d59476f19115c9774b3b447f78131781c6c32b", - "reference": "b8d59476f19115c9774b3b447f78131781c6c32b", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/747c3b2038f1139e3dcd9886a3f5a948648b7503", + "reference": "747c3b2038f1139e3dcd9886a3f5a948648b7503", "shasum": "" }, "require": { @@ -8147,7 +8147,7 @@ "sebastian/comparator": "^5.0", "sebastian/diff": "^5.0", "sebastian/environment": "^6.0", - "sebastian/exporter": "^5.0", + "sebastian/exporter": "^5.1", "sebastian/global-state": "^6.0.1", "sebastian/object-enumerator": "^5.0", "sebastian/recursion-context": "^5.0", @@ -8195,7 +8195,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.3.4" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.3.5" }, "funding": [ { @@ -8211,7 +8211,7 @@ "type": "tidelift" } ], - "time": "2023-09-12T14:42:28+00:00" + "time": "2023-09-19T05:42:37+00:00" }, { "name": "psr/cache", @@ -8697,16 +8697,16 @@ }, { "name": "sebastian/exporter", - "version": "5.0.1", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "32ff03d078fed1279c4ec9a407d08c5e9febb480" + "reference": "c3fa8483f9539b190f7cd4bfc4a07631dd1df344" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/32ff03d078fed1279c4ec9a407d08c5e9febb480", - "reference": "32ff03d078fed1279c4ec9a407d08c5e9febb480", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c3fa8483f9539b190f7cd4bfc4a07631dd1df344", + "reference": "c3fa8483f9539b190f7cd4bfc4a07631dd1df344", "shasum": "" }, "require": { @@ -8763,7 +8763,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.0" }, "funding": [ { @@ -8771,7 +8771,7 @@ "type": "github" } ], - "time": "2023-09-08T04:46:58+00:00" + "time": "2023-09-18T07:15:37+00:00" }, { "name": "sebastian/global-state", diff --git a/src/Controllers/AuthController.php b/src/Controllers/AuthController.php index 2a3cfbaed5..e37163bde3 100644 --- a/src/Controllers/AuthController.php +++ b/src/Controllers/AuthController.php @@ -5,6 +5,7 @@ namespace App\Controllers; use App\Models\InviteCode; +use App\Models\LoginIp; use App\Models\Setting; use App\Models\User; use App\Services\Auth; @@ -69,16 +70,16 @@ public function loginHandle(ServerRequest $request, Response $response, array $a } $antiXss = new AntiXSS(); - $code = $antiXss->xss_clean($request->getParam('code')); $passwd = $request->getParam('passwd'); $rememberMe = $request->getParam('remember_me') === 'true' ? 1 : 0; $email = strtolower(trim($antiXss->xss_clean($request->getParam('email')))); $redir = Cookie::get('redir') === '' ? $antiXss->xss_clean(Cookie::get('redir')) : '/user'; - $user = User::where('email', $email)->first(); if ($user === null) { + (new LoginIp())->collectInvalidUserLoginIP($_SERVER['REMOTE_ADDR'], 1); + return $response->withJson([ 'ret' => 0, 'msg' => '邮箱或者密码错误', diff --git a/src/Controllers/PasswordController.php b/src/Controllers/PasswordController.php index 00a3ecfbad..9b66b866d3 100644 --- a/src/Controllers/PasswordController.php +++ b/src/Controllers/PasswordController.php @@ -54,6 +54,7 @@ public function handleReset(ServerRequest $request, Response $response, array $a { if (Setting::obtain('enable_reset_password_captcha')) { $ret = Captcha::verify($request->getParams()); + if (! $ret) { return ResponseHelper::error($response, '系统无法接受你的验证结果,请刷新页面后重试'); } diff --git a/src/Controllers/SubController.php b/src/Controllers/SubController.php index 104ea9d6b8..6de1a21eec 100644 --- a/src/Controllers/SubController.php +++ b/src/Controllers/SubController.php @@ -66,7 +66,7 @@ public static function getUniversalSubContent($request, $response, $args): Respo . '; expire=' . strtotime($user->class_expire); if ($_ENV['subscribeLog']) { - SubscribeLog::add($user, $subtype, $request->getHeaderLine('User-Agent')); + (new SubscribeLog())->add($user, $subtype, $request->getHeaderLine('User-Agent')); } return $response->withHeader('Subscription-Userinfo', $sub_details) @@ -127,7 +127,7 @@ public static function getTraditionalSubContent($request, $response, $args): Res // 记录订阅日志 if ($_ENV['subscribeLog']) { - SubscribeLog::add($user, $sub_type, $request->getHeaderLine('User-Agent')); + (new SubscribeLog())->add($user, $sub_type, $request->getHeaderLine('User-Agent')); } $sub_details = ' upload=' . $user->u diff --git a/src/Controllers/UserController.php b/src/Controllers/UserController.php index 03a5b5dce0..cfdb28b9f5 100644 --- a/src/Controllers/UserController.php +++ b/src/Controllers/UserController.php @@ -141,6 +141,7 @@ public function invite(ServerRequest $request, Response $response, array $args): public function logout(ServerRequest $request, Response $response, array $args): Response { Auth::logout(); + return $response->withStatus(302)->withHeader('Location', '/'); } @@ -152,6 +153,7 @@ public function doCheckIn(ServerRequest $request, Response $response, array $arg if (Setting::obtain('enable_checkin_captcha')) { $ret = Captcha::verify($request->getParams()); + if (! $ret) { return ResponseHelper::error($response, '系统无法接受你的验证结果,请刷新页面后重试'); } diff --git a/src/Controllers/WebAPI/FuncController.php b/src/Controllers/WebAPI/FuncController.php index ce9033dc31..0e40f3a84b 100644 --- a/src/Controllers/WebAPI/FuncController.php +++ b/src/Controllers/WebAPI/FuncController.php @@ -25,7 +25,7 @@ public function getDetectRules(ServerRequest $request, Response $response, array { $rules = DetectRule::all(); - return ResponseHelper::etagJson($request, $response, [ + return ResponseHelper::successWithDataEtag($request, $response, [ 'ret' => 1, 'data' => $rules, ]); diff --git a/src/Controllers/WebAPI/NodeController.php b/src/Controllers/WebAPI/NodeController.php index b33732c253..2b731cec65 100644 --- a/src/Controllers/WebAPI/NodeController.php +++ b/src/Controllers/WebAPI/NodeController.php @@ -40,7 +40,7 @@ public function getInfo(ServerRequest $request, Response $response, array $args) 'version' => VERSION, ]; - return ResponseHelper::etagJson($request, $response, [ + return ResponseHelper::successWithDataEtag($request, $response, [ 'ret' => 1, 'data' => $data, ]); diff --git a/src/Controllers/WebAPI/UserController.php b/src/Controllers/WebAPI/UserController.php index 7f9364c268..0448be5f6a 100644 --- a/src/Controllers/WebAPI/UserController.php +++ b/src/Controllers/WebAPI/UserController.php @@ -124,7 +124,7 @@ public function index(ServerRequest $request, Response $response, array $args): $users[] = $user_raw; } - return ResponseHelper::etagJson($request, $response, [ + return ResponseHelper::successWithDataEtag($request, $response, [ 'ret' => 1, 'data' => $users, ]); diff --git a/src/Models/LoginIp.php b/src/Models/LoginIp.php index 5861214754..faf4a77208 100644 --- a/src/Models/LoginIp.php +++ b/src/Models/LoginIp.php @@ -4,6 +4,8 @@ namespace App\Models; +use function time; + /** * Ip Model */ @@ -21,7 +23,7 @@ public function user(): ?User } /** - * 登录用户 + * 登录用户名 */ public function userName(): string { @@ -35,4 +37,21 @@ public function type(): string { return $this->type === 0 ? '成功' : '失败'; } + + /** + * 记录登录 IP + * + * @param string $ip IP 地址 + * @param int $type 登录失败为 1 + * + * @return void + */ + public function collectInvalidUserLoginIP(string $ip, int $type = 0): void + { + $this->ip = $ip; + $this->userid = 0; + $this->datetime = time(); + $this->type = $type; + $this->save(); + } } diff --git a/src/Models/Model.php b/src/Models/Model.php index 7e8ffe6167..f8a56aaed9 100644 --- a/src/Models/Model.php +++ b/src/Models/Model.php @@ -17,6 +17,7 @@ abstract class Model extends EloquentModel public static function getTableName(): string { $class = static::class; + return (new $class())->getTable(); } } diff --git a/src/Models/Payback.php b/src/Models/Payback.php index cda8c411a6..44dbf7a1d8 100644 --- a/src/Models/Payback.php +++ b/src/Models/Payback.php @@ -33,23 +33,23 @@ public function getRefUserNameAttribute(): string User::where('id', $this->ref_by)->first()->user_name; } - public static function rebate($user_id, $order_amount): void + public function rebate($user_id, $order_amount): void { $configs = Setting::getClass('invite'); $user = User::where('id', $user_id)->first(); $gift_user_id = $user->ref_by; - // 判断 $invite_rebate_mode = (string) $configs['invite_rebate_mode']; if ($invite_rebate_mode === 'continued') { // 不设限制 - self::executeRebate($user_id, $gift_user_id, $order_amount); + $this->execute($user_id, $gift_user_id, $order_amount); } elseif ($invite_rebate_mode === 'limit_frequency') { // 限制返利次数 $rebate_frequency = self::where('userid', $user_id)->count(); + if ($rebate_frequency < $configs['rebate_frequency_limit']) { - self::executeRebate($user_id, $gift_user_id, $order_amount); + $this->execute($user_id, $gift_user_id, $order_amount); } } elseif ($invite_rebate_mode === 'limit_amount') { // 限制返利金额 @@ -61,20 +61,21 @@ public static function rebate($user_id, $order_amount): void && $total_rebate_amount <= $configs['rebate_amount_limit'] ) { $adjust_rebate = $configs['rebate_amount_limit'] - $total_rebate_amount; + if ($adjust_rebate > 0) { - self::executeRebate($user_id, $gift_user_id, $order_amount, $adjust_rebate); + $this->execute($user_id, $gift_user_id, $order_amount, $adjust_rebate); } } else { - self::executeRebate($user_id, $gift_user_id, $order_amount); + $this->execute($user_id, $gift_user_id, $order_amount); } } elseif ($invite_rebate_mode === 'limit_time_range') { if (strtotime($user->reg_date) + $configs['rebate_time_range_limit'] * 86400 > time()) { - self::executeRebate($user_id, $gift_user_id, $order_amount); + $this->execute($user_id, $gift_user_id, $order_amount); } } } - public static function executeRebate($user_id, $gift_user_id, $order_amount, $adjust_rebate = null): void + public function execute($user_id, $gift_user_id, $order_amount, $adjust_rebate = null): void { $gift_user = User::where('id', $gift_user_id)->first(); @@ -92,14 +93,13 @@ public static function executeRebate($user_id, $gift_user_id, $order_amount, $ad $adjust_rebate ?? $rebate_amount, '邀请用户 #' . $user_id . ' 返利', ); - // 记录 - $payback = new Payback(); - $payback->total = $order_amount; - $payback->userid = $user_id; - $payback->ref_by = $gift_user_id; - $payback->ref_get = $adjust_rebate ?? $rebate_amount; - $payback->datetime = time(); - $payback->save(); + // 添加记录 + $this->total = $order_amount; + $this->userid = $user_id; + $this->ref_by = $gift_user_id; + $this->ref_get = $adjust_rebate ?? $rebate_amount; + $this->datetime = time(); + $this->save(); } } } diff --git a/src/Models/SubscribeLog.php b/src/Models/SubscribeLog.php index 69e2172669..bad601a2a5 100644 --- a/src/Models/SubscribeLog.php +++ b/src/Models/SubscribeLog.php @@ -5,6 +5,7 @@ namespace App\Models; use App\Utils\Tools; +use Exception; use MaxMind\Db\Reader\InvalidDatabaseException; use voku\helper\AntiXSS; use function time; @@ -29,7 +30,7 @@ public function getLocationAttribute(): string { try { return Tools::getIpLocation($this->request_ip); - } catch (InvalidDatabaseException $e) { + } catch (InvalidDatabaseException|Exception $e) { return '未知'; } } @@ -37,15 +38,14 @@ public function getLocationAttribute(): string /** * 记录订阅日志 */ - public static function add(User $user, string $type, string $ua): void + public function add(User $user, string $type, string $ua): void { - $log = new SubscribeLog(); $antiXss = new AntiXSS(); - $log->user_id = $user->id; - $log->type = $antiXss->xss_clean($type); - $log->request_ip = $_SERVER['REMOTE_ADDR']; - $log->request_user_agent = $antiXss->xss_clean($ua); - $log->request_time = time(); - $log->save(); + $this->user_id = $user->id; + $this->type = $antiXss->xss_clean($type); + $this->request_ip = $_SERVER['REMOTE_ADDR']; + $this->request_user_agent = $antiXss->xss_clean($ua); + $this->request_time = time(); + $this->save(); } } diff --git a/src/Models/User.php b/src/Models/User.php index 26b65bd9d4..e9c0796246 100644 --- a/src/Models/User.php +++ b/src/Models/User.php @@ -393,7 +393,10 @@ public function sendDailyNotification(string $ann = ''): void /** * 记录登录 IP * + * @param string $ip IP 地址 * @param int $type 登录失败为 1 + * + * @return bool */ public function collectLoginIP(string $ip, int $type = 0): bool { diff --git a/src/Services/Gateway/AbstractPayment.php b/src/Services/Gateway/AbstractPayment.php index f33fe37be3..ca6e53d51c 100644 --- a/src/Services/Gateway/AbstractPayment.php +++ b/src/Services/Gateway/AbstractPayment.php @@ -78,7 +78,7 @@ public function postPayment($tradeno): false|int|string // 返利 if ($user->ref_by > 0 && Setting::obtain('invitation_mode') === 'after_paid') { - Payback::rebate($user->id, $paylist->total); + (new Payback())->rebate($user->id, $paylist->total); } return 0; diff --git a/src/Utils/Hash.php b/src/Utils/Hash.php index cf1629cc37..6502c2fd4f 100644 --- a/src/Utils/Hash.php +++ b/src/Utils/Hash.php @@ -25,12 +25,14 @@ public static function checkPassword($hashedPassword, $password): bool if (in_array($_ENV['pwdMethod'], ['bcrypt', 'argon2i', 'argon2id'])) { return password_verify($password, $hashedPassword); } + return $hashedPassword === self::passwordHash($password); } public static function passwordHash($pass): string { $method = $_ENV['pwdMethod']; + return match ($method) { 'md5' => self::md5WithSalt($pass), 'sha256' => self::sha256WithSalt($pass), @@ -43,12 +45,14 @@ public static function passwordHash($pass): string public static function md5WithSalt($pwd): string { $salt = $_ENV['salt']; + return md5($pwd . $salt); } public static function sha256WithSalt($pwd): string { $salt = $_ENV['salt']; + return hash('sha256', $pwd . $salt); } } diff --git a/src/Utils/ResponseHelper.php b/src/Utils/ResponseHelper.php index 8565fdeb62..37018f112a 100644 --- a/src/Utils/ResponseHelper.php +++ b/src/Utils/ResponseHelper.php @@ -20,6 +20,13 @@ public static function success(Response $response, string $msg): ResponseInterfa ]); } + /** + * @param Response $response + * @param string $msg + * @param array $data + * + * @return ResponseInterface + */ public static function successWithData(Response $response, string $msg, array $data): ResponseInterface { return $response->withJson([ @@ -37,6 +44,13 @@ public static function error(Response $response, string $msg): ResponseInterface ]); } + /** + * @param Response $response + * @param string $msg + * @param array $data + * + * @return ResponseInterface + */ public static function errorWithData(Response $response, string $msg, array $data): ResponseInterface { return $response->withJson([ @@ -57,7 +71,7 @@ public static function errorWithData(Response $response, string $msg, array $dat * * @return ResponseInterface */ - public static function etagJson( + public static function successWithDataEtag( RequestInterface $request, ResponseInterface $response, mixed $data From 1225076c2821af29d8ebafbbcf1f036716bfa623 Mon Sep 17 00:00:00 2001 From: M1Screw <14369594+M1Screw@users.noreply.github.com> Date: Sat, 23 Sep 2023 02:46:46 +0800 Subject: [PATCH 2/3] fix: missing file exists check in ClientDownload --- src/Command/ClientDownload.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Command/ClientDownload.php b/src/Command/ClientDownload.php index 608a28060f..2b1ed3ed53 100644 --- a/src/Command/ClientDownload.php +++ b/src/Command/ClientDownload.php @@ -263,7 +263,7 @@ private function getSoft(array $task): void echo '- 正在删除旧版本文件...' . PHP_EOL; - if (! unlink($filePath)) { + if (file_exists($filePath) && ! unlink($filePath)) { echo '- 删除旧版本文件失败,此任务跳过,请检查权限' . PHP_EOL; continue; } From 1e7eda716c471e72bbe8acac4dc18472347a1b60 Mon Sep 17 00:00:00 2001 From: M1Screw <14369594+M1Screw@users.noreply.github.com> Date: Sun, 24 Sep 2023 00:14:58 +0800 Subject: [PATCH 3/3] refactor: remove raw sql & better ss2022 pk gen --- app/routes.php | 6 +- composer.lock | 51 +++++---- config/appprofile.example.php | 4 +- config/clients.json | 24 ---- resources/views/tabler/admin/log/online.tpl | 2 +- resources/views/tabler/user/index.tpl | 14 --- src/Command/Tool.php | 3 +- src/Controllers/Admin/OnlineIpController.php | 106 ------------------ src/Controllers/Admin/OnlineLogController.php | 78 +++++++++++++ src/Controllers/User/ClientController.php | 2 - src/Controllers/WebAPI/UserController.php | 2 +- src/Models/OnlineLog.php | 10 ++ src/Models/User.php | 35 +----- src/Utils/Tools.php | 16 +++ 14 files changed, 141 insertions(+), 212 deletions(-) delete mode 100644 src/Controllers/Admin/OnlineIpController.php create mode 100644 src/Controllers/Admin/OnlineLogController.php diff --git a/app/routes.php b/app/routes.php index ff6ef4fd64..a88ddbf473 100644 --- a/app/routes.php +++ b/app/routes.php @@ -193,9 +193,9 @@ // 登录日志 $group->get('/login', App\Controllers\Admin\LoginLogController::class . ':index'); $group->post('/login/ajax', App\Controllers\Admin\LoginLogController::class . ':ajax'); - // 在线IP - $group->get('/online', App\Controllers\Admin\OnlineIpController::class . ':index'); - $group->post('/online/ajax', App\Controllers\Admin\OnlineIpController::class . ':ajax'); + // 在线IP日志 + $group->get('/online', App\Controllers\Admin\OnlineLogController::class . ':index'); + $group->post('/online/ajax', App\Controllers\Admin\OnlineLogController::class . ':ajax'); // 订阅日志 $group->get('/subscribe', App\Controllers\Admin\SubscribeLogController::class . ':index'); $group->post('/subscribe/ajax', App\Controllers\Admin\SubscribeLogController::class . ':ajax'); diff --git a/composer.lock b/composer.lock index c9a3ef1712..4daf3daf24 100644 --- a/composer.lock +++ b/composer.lock @@ -123,16 +123,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.281.11", + "version": "3.281.12", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "9d466efae67d5016ed132fd4ffa1566a7d4cab98" + "reference": "22a92f08758db2b152843ea0875eeee5a467d8ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/9d466efae67d5016ed132fd4ffa1566a7d4cab98", - "reference": "9d466efae67d5016ed132fd4ffa1566a7d4cab98", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/22a92f08758db2b152843ea0875eeee5a467d8ff", + "reference": "22a92f08758db2b152843ea0875eeee5a467d8ff", "shasum": "" }, "require": { @@ -212,9 +212,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.281.11" + "source": "https://github.com/aws/aws-sdk-php/tree/3.281.12" }, - "time": "2023-09-20T19:16:24+00:00" + "time": "2023-09-22T18:12:27+00:00" }, { "name": "bacon/bacon-qr-code", @@ -3646,16 +3646,16 @@ }, { "name": "psr/http-client", - "version": "1.0.2", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/php-fig/http-client.git", - "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31" + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31", - "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, "require": { @@ -3692,9 +3692,9 @@ "psr-18" ], "support": { - "source": "https://github.com/php-fig/http-client/tree/1.0.2" + "source": "https://github.com/php-fig/http-client" }, - "time": "2023-04-10T20:12:12+00:00" + "time": "2023-09-23T14:17:50+00:00" }, { "name": "psr/http-factory", @@ -4898,16 +4898,16 @@ }, { "name": "stripe/stripe-php", - "version": "v12.3.0", + "version": "v12.4.0", "source": { "type": "git", "url": "https://github.com/stripe/stripe-php.git", - "reference": "260aad072f92ddb05e03d47af13b3616d99b3444" + "reference": "7d0a90772fc1c179e370971264318208533324b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stripe/stripe-php/zipball/260aad072f92ddb05e03d47af13b3616d99b3444", - "reference": "260aad072f92ddb05e03d47af13b3616d99b3444", + "url": "https://api.github.com/repos/stripe/stripe-php/zipball/7d0a90772fc1c179e370971264318208533324b9", + "reference": "7d0a90772fc1c179e370971264318208533324b9", "shasum": "" }, "require": { @@ -4920,8 +4920,7 @@ "friendsofphp/php-cs-fixer": "3.5.0", "php-coveralls/php-coveralls": "^2.5", "phpstan/phpstan": "^1.2", - "phpunit/phpunit": "^5.7 || ^9.0", - "squizlabs/php_codesniffer": "^3.3" + "phpunit/phpunit": "^5.7 || ^9.0" }, "type": "library", "extra": { @@ -4953,9 +4952,9 @@ ], "support": { "issues": "https://github.com/stripe/stripe-php/issues", - "source": "https://github.com/stripe/stripe-php/tree/v12.3.0" + "source": "https://github.com/stripe/stripe-php/tree/v12.4.0" }, - "time": "2023-09-15T00:57:14+00:00" + "time": "2023-09-21T22:55:47+00:00" }, { "name": "symfony/deprecation-contracts", @@ -7110,16 +7109,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.27.0", + "version": "v3.28.0", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "e73ccaae1208f017bb7860986eebb3da48bd25d6" + "reference": "113e09fea3d2306319ffaa2423fe3de768b28cff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/e73ccaae1208f017bb7860986eebb3da48bd25d6", - "reference": "e73ccaae1208f017bb7860986eebb3da48bd25d6", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/113e09fea3d2306319ffaa2423fe3de768b28cff", + "reference": "113e09fea3d2306319ffaa2423fe3de768b28cff", "shasum": "" }, "require": { @@ -7193,7 +7192,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.27.0" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.28.0" }, "funding": [ { @@ -7201,7 +7200,7 @@ "type": "github" } ], - "time": "2023-09-17T14:37:54+00:00" + "time": "2023-09-22T20:43:40+00:00" }, { "name": "justinrainbow/json-schema", diff --git a/config/appprofile.example.php b/config/appprofile.example.php index 12f618e8d4..be5900d62a 100644 --- a/config/appprofile.example.php +++ b/config/appprofile.example.php @@ -102,11 +102,11 @@ ], 'route' => [ 'geoip' => [ - 'download_url' => 'https://cdn.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip-lite.db', + 'download_url' => 'https://' . $_ENV['jsdelivr_url'] . '/gh/MetaCubeX/meta-rules-dat@release/geoip-lite.db', 'download_detour' => 'direct', ], 'geosite' => [ - 'download_url' => 'https://cdn.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite-lite.db', + 'download_url' => 'https://' . $_ENV['jsdelivr_url'] . '/gh/MetaCubeX/meta-rules-dat@release/geosite-lite.db', 'download_detour' => 'direct', ], 'rules' => [ diff --git a/config/clients.json b/config/clients.json index 62ba5bf1a4..f913d61475 100644 --- a/config/clients.json +++ b/config/clients.json @@ -32,30 +32,6 @@ } ] }, - { - "name": "v2rayN", - "tagMethod": "github_pre_release", - "gitRepo": "2dust/v2rayN", - "savePath": "public/clients/", - "downloads": [ - { - "sourceName": "v2rayN-With-Core.zip", - "saveName": "v2rayN-Core.zip" - } - ] - }, - { - "name": "v2rayNG", - "tagMethod": "github_pre_release", - "gitRepo": "2dust/v2rayNG", - "savePath": "public/clients/", - "downloads": [ - { - "sourceName": "v2rayNG_%tagName%.apk", - "saveName": "v2rayNG.apk" - } - ] - }, { "name": "sing-box", "tagMethod": "github_release", diff --git a/resources/views/tabler/admin/log/online.tpl b/resources/views/tabler/admin/log/online.tpl index bdb1cf54f3..23b8163052 100644 --- a/resources/views/tabler/admin/log/online.tpl +++ b/resources/views/tabler/admin/log/online.tpl @@ -45,7 +45,7 @@ ajax: { url: '/admin/online/ajax', type: 'POST', - dataSrc: 'onlines' + dataSrc: 'onlines.data' }, "autoWidth":false, 'iDisplayLength': 10, diff --git a/resources/views/tabler/user/index.tpl b/resources/views/tabler/user/index.tpl index 8047d3afe9..227613e4e9 100644 --- a/resources/views/tabler/user/index.tpl +++ b/resources/views/tabler/user/index.tpl @@ -255,20 +255,6 @@ 复制传统订阅(Trojan) {/if} - - 下载 v2rayN(Windows) - - - 下载 v2rayNG(Android) - diff --git a/src/Command/Tool.php b/src/Command/Tool.php index 53b1e91f50..ea5e51f6a9 100644 --- a/src/Command/Tool.php +++ b/src/Command/Tool.php @@ -247,7 +247,8 @@ public function generateUUID(): void $users = ModelsUser::all(); foreach ($users as $user) { - $user->generateUUID(); + $user->uuid = Uuid::uuid4(); + $user->save(); } echo 'generate UUID successful'; diff --git a/src/Controllers/Admin/OnlineIpController.php b/src/Controllers/Admin/OnlineIpController.php deleted file mode 100644 index a32b121143..0000000000 --- a/src/Controllers/Admin/OnlineIpController.php +++ /dev/null @@ -1,106 +0,0 @@ - [ - 'id' => '事件ID', - 'user_id' => '用户ID', - 'user_name' => '用户名', - 'node_id' => '节点ID', - 'node_name' => '节点名', - 'ip' => 'IP', - 'location' => 'IP归属地', - 'first_time' => '首次连接', - 'last_time' => '最后连接', - ], - ]; - - /** - * 后台在线 IP 页面 - * - * @throws Exception - */ - public function index(ServerRequest $request, Response $response, array $args): Response|ResponseInterface - { - return $response->write( - $this->view() - ->assign('details', self::$details) - ->fetch('admin/log/online.tpl') - ); - } - - /** - * 后台在线 IP 页面 AJAX - * - * @throws InvalidDatabaseException - */ - public function ajax(ServerRequest $request, Response $response, array $args): Response|ResponseInterface - { - $data = $request->getParsedBody(); - $length = (int) ($data['length'] ?? 0); - $start = (int) ($data['start'] ?? 0); - $draw = $data['draw'] ?? null; - - $logs = DB::select(' - SELECT - online_log.id, - online_log.user_id, - user.user_name, - online_log.node_id, - node.name AS node_name, - online_log.ip, - online_log.first_time, - online_log.last_time - FROM - online_log - LEFT JOIN user ON user.id = online_log.user_id - LEFT JOIN node ON node.id = online_log.node_id - WHERE - last_time > UNIX_TIMESTAMP() - 90 - '); - - $count = count($logs); - $data = array_map( - static function ($val) { - return [ - 'id' => $val->id, - 'user_id' => $val->user_id, - 'user_name' => $val->user_name, - 'node_id' => $val->node_id, - 'node_name' => $val->node_name, - 'ip' => str_replace('::ffff:', '', $val->ip), - 'location' => Tools::getIpLocation(str_replace('::ffff:', '', $val->ip)), - 'first_time' => Tools::toDateTime($val->first_time), - 'last_time' => Tools::toDateTime($val->last_time), - ]; - }, - array_slice($logs, $start, $length) - ); - - return $response->withJson([ - 'draw' => $draw, - 'recordsTotal' => $count, - 'recordsFiltered' => $count, - 'onlines' => $data, - ]); - } -} diff --git a/src/Controllers/Admin/OnlineLogController.php b/src/Controllers/Admin/OnlineLogController.php new file mode 100644 index 0000000000..6a3580b66c --- /dev/null +++ b/src/Controllers/Admin/OnlineLogController.php @@ -0,0 +1,78 @@ + [ + 'id' => '事件ID', + 'user_id' => '用户ID', + 'user_name' => '用户名', + 'node_id' => '节点ID', + 'node_name' => '节点名', + 'ip' => 'IP', + 'location' => 'IP归属地', + 'first_time' => '首次连接', + 'last_time' => '最后连接', + ], + ]; + + /** + * 后台在线 IP 页面 + * + * @throws Exception + */ + public function index(ServerRequest $request, Response $response, array $args): Response|ResponseInterface + { + return $response->write( + $this->view() + ->assign('details', self::$details) + ->fetch('admin/log/online.tpl') + ); + } + + /** + * 后台在线 IP 页面 AJAX + * + * @throws InvalidDatabaseException + */ + public function ajax(ServerRequest $request, Response $response, array $args): Response|ResponseInterface + { + $length = $request->getParam('length'); + $page = $request->getParam('start') / $length + 1; + $draw = $request->getParam('draw'); + + $onlines = OnlineLog::where('last_time', '>', time() - 90)->orderByDesc('last_time')->paginate($length, '*', '', $page); + $total = OnlineLog::where('last_time', '>', time() - 90)->count(); + + foreach ($onlines as $online) { + $online->user_name = $online->userName(); + $online->node_name = $online->nodeName(); + $online->ip = $online->ip(); + $online->location = Tools::getIpLocation($online->ip); + $online->first_time = Tools::toDateTime($online->first_time); + $online->last_time = Tools::toDateTime($online->last_time); + } + + return $response->withJson([ + 'draw' => $draw, + 'recordsTotal' => $total, + 'recordsFiltered' => $total, + 'onlines' => $onlines, + ]); + } +} diff --git a/src/Controllers/User/ClientController.php b/src/Controllers/User/ClientController.php index 59d94fd709..a2c90eacd3 100644 --- a/src/Controllers/User/ClientController.php +++ b/src/Controllers/User/ClientController.php @@ -28,8 +28,6 @@ public function getClients(ServerRequest $request, Response $response, array $ar 'Clash.Verge_aarch64.dmg', 'Clash.Verge.AppImage.tar.gz', 'Clash-Android.apk', - 'v2rayN-Core.zip', - 'v2rayNG.apk', 'SFA.apk', 'SFM.zip', ]; diff --git a/src/Controllers/WebAPI/UserController.php b/src/Controllers/WebAPI/UserController.php index 0448be5f6a..349f74dc0e 100644 --- a/src/Controllers/WebAPI/UserController.php +++ b/src/Controllers/WebAPI/UserController.php @@ -112,7 +112,7 @@ public function index(ServerRequest $request, Response $response, array $args): default => 32, }; - $user_raw->passwd = $user_raw->getSs2022Pk($pk_len); + $user_raw->passwd = Tools::genSs2022UserPk($user_raw->passwd, $pk_len); } $user_raw->node_connector = 0; diff --git a/src/Models/OnlineLog.php b/src/Models/OnlineLog.php index a99279c804..3df5f70859 100644 --- a/src/Models/OnlineLog.php +++ b/src/Models/OnlineLog.php @@ -55,4 +55,14 @@ public function ip(): string return $ip; } + + public function userName(): string + { + return User::where('id', $this->user_id)->value('user_name'); + } + + public function nodeName(): string + { + return Node::where('id', $this->node_id)->value('name'); + } } diff --git a/src/Models/User.php b/src/Models/User.php index e9c0796246..e4928850c6 100644 --- a/src/Models/User.php +++ b/src/Models/User.php @@ -4,7 +4,6 @@ namespace App\Models; -use App\Services\DB; use App\Services\IM; use App\Utils\Hash; use App\Utils\Tools; @@ -51,15 +50,7 @@ final class User extends Model */ public function getSs2022Pk($len): string { - $passwd_hash = hash('sha256', $this->passwd); - - $pk = match ($len) { - 16 => mb_strcut($passwd_hash, 0, 16), - 32 => mb_strcut($passwd_hash, 0, 32), - default => $passwd_hash, - }; - - return base64_encode($pk); + return Tools::genSs2022UserPk($this->passwd, $len); } /** @@ -67,7 +58,7 @@ public function getSs2022Pk($len): string */ public function getDiceBearAttribute(): string { - return 'https://api.dicebear.com/6.x/identicon/svg?seed=' . md5($this->email); + return 'https://api.dicebear.com/7.x/identicon/svg?seed=' . md5($this->email); } /** @@ -131,16 +122,6 @@ public function addInviteNum(int $num): bool return $this->save(); } - /** - * 生成新的 UUID - */ - public function generateUUID(): bool - { - $this->uuid = Uuid::uuid4(); - - return $this->save(); - } - /** * 生成新的 API Token */ @@ -281,17 +262,7 @@ public function getTopUp(): float */ public function onlineIpCount(): int { - return DB::select( - ' - SELECT - COUNT(*) AS count - FROM - online_log - WHERE - user_id = ? - AND last_time >= UNIX_TIMESTAMP() - 90', - [$this->attributes['id']] - )[0]->count; + return OnlineLog::where('user_id', $this->id)->where('last_time', '>', time() - 90)->count(); } /** diff --git a/src/Utils/Tools.php b/src/Utils/Tools.php index 2906b0cb39..3701d48649 100644 --- a/src/Utils/Tools.php +++ b/src/Utils/Tools.php @@ -12,16 +12,19 @@ use MaxMind\Db\Reader\InvalidDatabaseException; use function array_diff; use function array_flip; +use function base64_encode; use function bin2hex; use function closedir; use function date; use function explode; use function filter_var; use function floor; +use function hash; use function in_array; use function is_numeric; use function json_decode; use function log; +use function mb_strcut; use function opendir; use function openssl_random_pseudo_bytes; use function pow; @@ -215,6 +218,19 @@ public static function genSubToken(): string return "couldn't alloc token"; } + public static function genSs2022UserPk($passwd, $len): string + { + $passwd_hash = hash('sha256', $passwd); + + $pk = match ($len) { + 16 => mb_strcut($passwd_hash, 0, 16), + 32 => mb_strcut($passwd_hash, 0, 32), + default => $passwd_hash, + }; + + return base64_encode($pk); + } + public static function toDateTime(int $time): string { return date('Y-m-d H:i:s', $time);