Kaynağa Gözat

feat: htmx in /auth & /password

M1Screw 1 yıl önce
ebeveyn
işleme
09b45e71f5

+ 4 - 3
app/routes.php

@@ -3,9 +3,9 @@
 declare(strict_types=1);
 
 use App\Middleware\Admin;
-use App\Middleware\Auth;
 use App\Middleware\Guest;
 use App\Middleware\NodeToken;
+use App\Middleware\User;
 use Slim\Routing\RouteCollectorProxy;
 
 return static function (Slim\App $app): void {
@@ -24,7 +24,8 @@ return static function (Slim\App $app): void {
     $app->get('/oauth/{type}', App\Controllers\OAuthController::class . ':index');
     // 通用订阅
     $app->get('/sub/{token}/{subtype}', App\Controllers\SubController::class . ':getUniversalSubContent');
-    // User Center
+
+    // User
     $app->group('/user', static function (RouteCollectorProxy $group): void {
         $group->get('', App\Controllers\UserController::class . ':index');
         $group->get('/', App\Controllers\UserController::class . ':index');
@@ -106,7 +107,7 @@ return static function (Slim\App $app): void {
         $group->get('/clients/{name}', App\Controllers\User\ClientController::class . ':getClients');
         // 登出
         $group->get('/logout', App\Controllers\UserController::class . ':logout');
-    })->add(new Auth());
+    })->add(new User());
 
     $app->group('/payment', static function (RouteCollectorProxy $group): void {
         $group->get('/notify/{type}', App\Services\Payment::class . ':notify');

+ 26 - 73
resources/views/tabler/auth/login.tpl

@@ -12,7 +12,7 @@
             <div class="card-body">
                 <h2 class="card-title text-center mb-4">登录到用户中心</h2>
                 <div class="mb-3">
-                    <label class="form-label">注册邮箱</label>
+                    <label class="form-label">邮箱</label>
                     <input id="email" type="email" class="form-control">
                 </div>
                 <div class="mb-2">
@@ -28,7 +28,7 @@
                 </div>
                 <div class="mb-2">
                     <label class="form-label">两步认证</label>
-                    <input id="code" type="email" class="form-control" placeholder="如果没有设置两步认证可留空">
+                    <input id="mfa_code" type="email" class="form-control" placeholder="如果没有设置两步认证可留空">
                 </div>
                 <div class="mb-2">
                     <label class="form-check">
@@ -36,25 +36,31 @@
                         <span class="form-check-label">记住此设备</span>
                     </label>
                 </div>
-                {if $public_setting['enable_login_captcha']}
-                    {if $public_setting['captcha_provider'] === 'turnstile'}
-                        <div class="mb-3">
-                            <div class="input-group mb-3">
-                                <div id="cf-turnstile" class="cf-turnstile"
-                                     data-sitekey="{$captcha['turnstile_sitekey']}" data-theme="light"></div>
-                            </div>
-                        </div>
-                    {/if}
-                    {if $public_setting['captcha_provider'] === 'geetest'}
-                        <div class="mb-3">
-                            <div class="input-group mb-3">
-                                <div id="geetest"></div>
-                            </div>
-                        </div>
+                <div class="mb-3">
+                    <div class="input-group mb-3">
+                    {if $public_setting['enable_login_captcha']}
+                        {include file='captcha_div.tpl'}
                     {/if}
-                {/if}
+                    </div>
+                </div>
                 <div class="form-footer">
-                    <button id="login-dashboard" class="btn btn-primary w-100">登录</button>
+                    <button id="login" class="btn btn-primary w-100"
+                            hx-post="/auth/login" hx-swap="none" hx-vals='js:{
+                                {if $public_setting['enable_login_captcha']}
+                                    {if $public_setting['captcha_provider'] === 'turnstile'}
+                                        turnstile: document.querySelector("[name=cf-turnstile-response]").value,
+                                    {/if}
+                                    {if $public_setting['captcha_provider'] === 'geetest'}
+                                        geetest: geetest_result,
+                                    {/if}
+                                {/if}
+                                email: document.getElementById("email").value,
+                                passwd: document.getElementById("passwd").value,
+                                mfa_code: document.getElementById("mfa_code").value,
+                                remember_me: document.getElementById("remember_me").checked,
+                             }'>
+                        登录
+                    </button>
                 </div>
             </div>
         </div>
@@ -64,61 +70,8 @@
     </div>
 </div>
 
-<script>
-    $("#login-dashboard").click(function () {
-        $.ajax({
-            type: 'POST',
-            url: '/auth/login',
-            dataType: "json",
-            data: {
-                code: $('#code').val(),
-                email: $('#email').val(),
-                passwd: $('#passwd').val(),
-                remember_me: $('#remember_me').is(":checked"),
-                {if $public_setting['enable_login_captcha']}
-                {if $public_setting['captcha_provider'] === 'turnstile'}
-                turnstile: $('input[name=cf-turnstile-response]').val(),
-                {/if}
-                {if $public_setting['captcha_provider'] === 'geetest'}
-                geetest: geetest_result,
-                {/if}
-                {/if}
-            },
-            success: function (data) {
-                if (data.ret === 1) {
-                    $('#success-message').text(data.msg);
-                    $('#success-dialog').modal('show');
-                    window.setTimeout(location.href = data.redir, {$config['jump_delay']});
-                } else {
-                    $('#fail-message').text(data.msg);
-                    $('#fail-dialog').modal('show');
-                }
-            }
-        })
-    });
-</script>
-
 {if $public_setting['enable_login_captcha']}
-{if $public_setting['captcha_provider'] === 'turnstile'}
-    <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
-{/if}
-{if $public_setting['captcha_provider'] === 'geetest'}
-    <script src="https://static.geetest.com/v4/gt4.js"></script>
-    <script>
-        let geetest_result = '';
-        initGeetest4({
-            captchaId: '{$captcha['geetest_id']}',
-            product: 'float',
-            language: "zho",
-            riskType: 'slide'
-        }, function (geetest) {
-            geetest.appendTo("#geetest");
-            geetest.onSuccess(function () {
-                geetest_result = geetest.getValidate();
-            });
-        });
-    </script>
-{/if}
+    {include file='captcha_js.tpl'}
 {/if}
 
 {include file='footer.tpl'}

+ 37 - 101
resources/views/tabler/auth/register.tpl

@@ -31,9 +31,9 @@
                     {if $public_setting['reg_mode'] !== 'close' }
                         <div class="mb-3">
                             <div class="input-group input-group-flat">
-                                <input id="code" type="text" class="form-control"
+                                <input id="invite_code" type="text" class="form-control"
                                        placeholder="注册邀请码{if $public_setting['reg_mode'] === 'open'}(可选){else}(必填){/if}"
-                                       value="{$code}">
+                                       value="{$invite_code}">
                             </div>
                         </div>
                     {/if}
@@ -41,7 +41,11 @@
                         <div class="mb-3">
                             <div class="input-group mb-2">
                                 <input id="emailcode" type="text" class="form-control" placeholder="邮箱验证码">
-                                <button id="email-verify" class="btn text-blue" type="button">获取</button>
+                                <button id="send-verify-email" class="btn text-blue" type="button"
+                                        hx-post="/auth/send" hx-swap="none"
+                                        hx-vals='js:{ email: document.getElementById("email").value }'>
+                                    获取
+                                </button>
                             </div>
                         </div>
                     {/if}
@@ -53,25 +57,36 @@
                                 </span>
                         </label>
                     </div>
-                    {if $public_setting['enable_reg_captcha']}
-                        {if $public_setting['captcha_provider'] === 'turnstile'}
-                            <div class="mb-3">
-                                <div class="input-group mb-3">
-                                    <div id="cf-turnstile" class="cf-turnstile"
-                                         data-sitekey="{$captcha['turnstile_sitekey']}" data-theme="light"></div>
-                                </div>
-                            </div>
-                        {/if}
-                        {if $public_setting['captcha_provider'] === 'geetest'}
-                            <div class="mb-3">
-                                <div class="input-group mb-3">
-                                    <div id="geetest"></div>
-                                </div>
-                            </div>
+                    <div class="mb-3">
+                        <div class="input-group mb-3">
+                        {if $public_setting['enable_reg_captcha']}
+                            {include file='captcha_div.tpl'}
                         {/if}
-                    {/if}
+                        </div>
+                    </div>
                     <div class="form-footer">
-                        <button id="confirm-register" type="submit" class="btn btn-primary w-100">注册新账户</button>
+                        <button id="register" class="btn btn-primary w-100"
+                                hx-post="/auth/register" hx-swap="none" hx-vals='js:{
+                                    {if $public_setting['reg_email_verify']}
+                                        emailcode: document.getElementById("emailcode").value,
+                                    {/if}
+                                    {if $public_setting['enable_reg_captcha']}
+                                        {if $public_setting['captcha_provider'] === 'turnstile'}
+                                            turnstile: document.querySelector("[name=cf-turnstile-response]").value,
+                                        {/if}
+                                        {if $public_setting['captcha_provider'] === 'geetest'}
+                                            geetest: geetest_result,
+                                        {/if}
+                                    {/if}
+                                    name: document.getElementById("name").value,
+                                    email: document.getElementById("email").value,
+                                    passwd: document.getElementById("passwd").value,
+                                    repasswd: document.getElementById("repasswd").value,
+                                    invite_code: document.getElementById("invite_code").value,
+                                    tos: document.getElementById("tos").checked,
+                                 }'>
+                            注册新账户
+                        </button>
                     </div>
                 </div>
             {else}
@@ -86,87 +101,8 @@
     </div>
 </div>
 
-<script>
-    {if $public_setting['reg_email_verify']}
-    $("#email-verify").click(function () {
-        $.ajax({
-            type: 'POST',
-            url: '/auth/send',
-            dataType: "json",
-            data: {
-                email: $('#email').val(),
-            },
-            success: function (data) {
-                if (data.ret === 1) {
-                    $('#success-message').text(data.msg);
-                    $('#success-dialog').modal('show');
-                } else {
-                    $('#fail-message').text(data.msg);
-                    $('#fail-dialog').modal('show');
-                }
-            }
-        })
-    });
-    {/if}
-
-    $("#confirm-register").click(function () {
-        $.ajax({
-            type: 'POST',
-            url: '/auth/register',
-            dataType: "json",
-            data: {
-                {if $public_setting['reg_email_verify']}
-                emailcode: $('#emailcode').val(),
-                {/if}
-                tos: $('#tos').prop('checked'), // true / false (string)
-                code: $('#code').val(),
-                name: $('#name').val(),
-                email: $('#email').val(),
-                passwd: $('#passwd').val(),
-                repasswd: $('#repasswd').val(),
-                {if $public_setting['enable_reg_captcha']}
-                {if $public_setting['captcha_provider'] === 'turnstile'}
-                turnstile: $('input[name=cf-turnstile-response]').val(),
-                {/if}
-                {if $public_setting['captcha_provider'] === 'geetest'}
-                geetest: geetest_result,
-                {/if}
-                {/if}
-            },
-            success: function (data) {
-                if (data.ret === 1) {
-                    $('#success-message').text(data.msg);
-                    $('#success-dialog').modal('show');
-                    window.setTimeout(location.href = data.redir, {$config['jump_delay']});
-                } else {
-                    $('#fail-message').text(data.msg);
-                    $('#fail-dialog').modal('show');
-                }
-            }
-        })
-    });
-</script>
-
 {if $public_setting['enable_reg_captcha']}
-{if $public_setting['captcha_provider'] === 'turnstile'}
-    <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
-{/if}
-{if $public_setting['captcha_provider'] === 'geetest'}
-    <script src="https://static.geetest.com/v4/gt4.js"></script>
-    <script>
-        let geetest_result = '';
-        initGeetest4({
-            captchaId: '{$captcha['geetest_id']}',
-            product: 'float',
-            language: "zho",
-            riskType: 'slide'
-        }, function (geetest) {
-            geetest.appendTo("#geetest");
-            geetest.onSuccess(function () {
-                geetest_result = geetest.getValidate();
-            });
-        });
-    </script>
-{/if}
+    {include file='captcha_js.tpl'}
 {/if}
+
 {include file='footer.tpl'}

+ 6 - 0
resources/views/tabler/captcha_div.tpl

@@ -0,0 +1,6 @@
+{if $public_setting['captcha_provider'] === 'turnstile'}
+    <div id="cf-turnstile" class="cf-turnstile" data-sitekey="{$captcha['turnstile_sitekey']}" data-theme="light"></div>
+{/if}
+{if $public_setting['captcha_provider'] === 'geetest'}
+    <div id="geetest"></div>
+{/if}

+ 20 - 0
resources/views/tabler/captcha_js.tpl

@@ -0,0 +1,20 @@
+{if $public_setting['captcha_provider'] === 'turnstile'}
+    <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
+{/if}
+{if $public_setting['captcha_provider'] === 'geetest'}
+    <script src="https://static.geetest.com/v4/gt4.js"></script>
+    <script>
+        let geetest_result = '';
+        initGeetest4({
+            captchaId: '{$captcha['geetest_id']}',
+            product: 'float',
+            language: "zho",
+            riskType: 'slide'
+        }, function (geetest) {
+            geetest.appendTo("#geetest");
+            geetest.onSuccess(function () {
+                geetest_result = geetest.getValidate();
+            });
+        });
+    </script>
+{/if}

+ 21 - 0
resources/views/tabler/footer.tpl

@@ -48,6 +48,27 @@
 
 <script src="//{$config['jsdelivr_url']}/npm/@tabler/core@latest/dist/js/tabler.min.js"></script>
 
+<script>
+    htmx.on("htmx:afterRequest", function(evt) {
+        if (evt.detail.xhr.getResponseHeader('HX-Redirect'))
+        {
+            return;
+        }
+
+        let res = JSON.parse(evt.detail.xhr.response);
+        let successDialog = new bootstrap.Modal(document.getElementById('success-dialog'));
+        let failDialog = new bootstrap.Modal(document.getElementById('fail-dialog'));
+
+        if (res.ret === 1) {
+            document.getElementById("success-message").innerHTML = res.msg;
+            successDialog.show();
+        } else {
+            document.getElementById("fail-message").innerHTML = res.msg;
+            failDialog.show();
+        }
+    });
+</script>
+
 {include file='live_chat.tpl'}
 
 </body>

+ 1 - 1
resources/views/tabler/header.tpl

@@ -13,5 +13,5 @@
     <link href="//{$config['jsdelivr_url']}/npm/@tabler/icons-webfont@latest/tabler-icons.min.css" rel="stylesheet"/>
     <!-- JS files -->
     <script src="/assets/js/fuck.min.js"></script>
-    <script src="//{$config['jsdelivr_url']}/npm/jquery/dist/jquery.min.js"></script>
+    <script src="//{$config['jsdelivr_url']}/npm/htmx.org@latest/dist/htmx.min.js"></script>
 </head>

+ 19 - 67
resources/views/tabler/password/reset.tpl

@@ -18,25 +18,26 @@
                     <label class="form-label">注册邮箱</label>
                     <input id="email" type="email" class="form-control">
                 </div>
-                {if $public_setting['enable_reset_password_captcha']}
-                    {if $public_setting['captcha_provider'] === 'turnstile'}
-                        <div class="mb-3">
-                            <div class="input-group mb-3">
-                                <div id="cf-turnstile" class="cf-turnstile"
-                                     data-sitekey="{$captcha['turnstile_sitekey']}" data-theme="light"></div>
-                            </div>
-                        </div>
-                    {/if}
-                    {if $public_setting['captcha_provider'] === 'geetest'}
-                        <div class="mb-3">
-                            <div class="input-group mb-3">
-                                <div id="geetest"></div>
-                            </div>
-                        </div>
+                <div class="mb-3">
+                    <div class="input-group mb-3">
+                    {if $public_setting['enable_reset_password_captcha']}
+                        {include file='captcha_div.tpl'}
                     {/if}
-                {/if}
+                    </div>
+                </div>
                 <div class="form-footer">
-                    <button id="send" class="btn btn-primary w-100">
+                    <button id="send" class="btn btn-primary w-100"
+                        hx-post="/password/reset" hx-swap="none" hx-vals='js:{
+                            {if $public_setting['enable_reset_password_captcha']}
+                                {if $public_setting['captcha_provider'] === 'turnstile'}
+                                    turnstile: document.querySelector("[name=cf-turnstile-response]").value,
+                                {/if}
+                                {if $public_setting['captcha_provider'] === 'geetest'}
+                                    geetest: geetest_result,
+                                {/if}
+                            {/if}
+                            email: document.getElementById("email").value,
+                         }'>
                         <i class="ti ti-brand-telegram icon"></i>
                         发送邮件
                     </button>
@@ -49,56 +50,7 @@
     </div>
 </div>
 
-<script>
-    $("#send").click(function () {
-        $.ajax({
-            type: 'POST',
-            url: '/password/reset',
-            dataType: "json",
-            data: {
-                email: $('#email').val(),
-                {if $public_setting['enable_reset_password_captcha']}
-                {if $public_setting['captcha_provider'] === 'turnstile'}
-                turnstile: $('input[name=cf-turnstile-response]').val(),
-                {/if}
-                {if $public_setting['captcha_provider'] === 'geetest'}
-                geetest: geetest_result,
-                {/if}
-                {/if}
-            },
-            success: function (data) {
-                if (data.ret === 1) {
-                    $('#success-message').text(data.msg);
-                    $('#success-dialog').modal('show');
-                } else {
-                    $('#fail-message').text(data.msg);
-                    $('#fail-dialog').modal('show');
-                }
-            }
-        })
-    });
-</script>
-
 {if $public_setting['enable_reset_password_captcha']}
-{if $public_setting['captcha_provider'] === 'turnstile'}
-    <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
-{/if}
-{if $public_setting['captcha_provider'] === 'geetest'}
-    <script src="https://static.geetest.com/v4/gt4.js"></script>
-    <script>
-        let geetest_result = '';
-        initGeetest4({
-            captchaId: '{$captcha['geetest_id']}',
-            product: 'float',
-            language: "zho",
-            riskType: 'slide'
-        }, function (geetest) {
-            geetest.appendTo("#geetest");
-            geetest.onSuccess(function () {
-                geetest_result = geetest.getValidate();
-            });
-        });
-    </script>
-{/if}
+    {include file='captcha_js.tpl'}
 {/if}
 {include file='footer.tpl'}

+ 5 - 27
resources/views/tabler/password/token.tpl

@@ -11,9 +11,6 @@
         <div class="card card-md">
             <div class="card-body">
                 <h2 class="card-title text-center mb-4">设置新密码</h2>
-                <p class="text-secondary mb-4">
-                    请在下方设置账户新的登录密码
-                </p>
                 <div class="mb-3">
                     <label class="form-label">新密码</label>
                     <input id="password" type="password" class="form-control" placeholder="请输入新密码">
@@ -23,7 +20,11 @@
                     <input id="repasswd" type="password" class="form-control" placeholder="请再次输入新密码">
                 </div>
                 <div class="form-footer">
-                    <button id="reset" class="btn btn-primary w-100">
+                    <button id="reset" class="btn btn-primary w-100"
+                            hx-post="{ location.pathname }" hx-swap="none"
+                            hx-vals='js:{
+                            password: document.getElementById("password").value,
+                            repasswd: document.getElementById("repasswd").value, }'>
                         <i class="ti ti-key icon"></i>
                         重置
                     </button>
@@ -36,27 +37,4 @@
     </div>
 </div>
 
-<script>
-    $("#reset").click(function () {
-        $.ajax({
-            type: 'POST',
-            url: location.pathname,
-            dataType: "json",
-            data: {
-                password: $('#password').val(),
-                repasswd: $('#repasswd').val(),
-            },
-            success: function (data) {
-                if (data.ret === 1) {
-                    $('#success-message').text(data.msg);
-                    $('#success-dialog').modal('show');
-                } else {
-                    $('#fail-message').text(data.msg);
-                    $('#fail-dialog').modal('show');
-                }
-            }
-        })
-    });
-</script>
-
 {include file='footer.tpl'}

+ 3 - 33
resources/views/tabler/user/index.tpl

@@ -549,19 +549,7 @@
                                         <button id="check-in" class="btn btn-primary ms-auto" disabled>已签到</button>
                                     {else}
                                         {if $public_setting['enable_checkin_captcha']}
-                                            {if $public_setting['captcha_provider'] === 'turnstile'}
-                                                <div id="cf-turnstile" class="cf-turnstile"
-                                                     data-sitekey="{$captcha['turnstile_sitekey']}"
-                                                        {if $user->is_dark_mode}
-                                                            data-theme="dark"
-                                                        {else}
-                                                            data-theme="light"
-                                                        {/if}
-                                                ></div>
-                                            {/if}
-                                            {if $public_setting['captcha_provider'] === 'geetest'}
-                                                <div id="geetest"></div>
-                                            {/if}
+                                            {include file='captcha_div.tpl'}
                                         {/if}
                                         <button id="check-in" class="btn btn-primary ms-auto"
                                                 hx-post="/user/checkin" hx-swap="none"
@@ -584,25 +572,7 @@
     </div>
 
     {if $public_setting['enable_checkin_captcha'] && $user->isAbleToCheckin()}
-        {if $public_setting['captcha_provider'] === 'turnstile'}
-            <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
-        {/if}
-        {if $public_setting['captcha_provider'] === 'geetest'}
-            <script src="https://static.geetest.com/v4/gt4.js"></script>
-            <script>
-                let geetest_result = '';
-                initGeetest4({
-                    captchaId: '{$captcha['geetest_id']}',
-                    product: 'float',
-                    language: "zho",
-                    riskType: 'slide'
-                }, function (geetest) {
-                    geetest.appendTo("#geetest");
-                    geetest.onSuccess(function () {
-                        geetest_result = geetest.getValidate();
-                    });
-                });
-            </script>
-        {/if}
+        {include file='captcha_js.tpl'}
     {/if}
+
     {include file='user/footer.tpl'}

+ 1 - 1
src/Controllers/Admin/UserController.php

@@ -140,7 +140,7 @@ final class UserController extends BaseController
             $password = Tools::genRandomChar(16);
         }
 
-        AuthController::registerHelper($response, 'user', $email, $password, '', 0, '', $balance, 1);
+        (new AuthController())->registerHelper($response, 'user', $email, $password, '', 0, '', $balance, 1);
         $user = (new User())->where('email', $email)->first();
 
         if ($ref_by !== '') {

+ 26 - 33
src/Controllers/AuthController.php

@@ -25,7 +25,6 @@ use Ramsey\Uuid\Uuid;
 use RedisException;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
-use voku\helper\AntiXSS;
 use function array_rand;
 use function date;
 use function explode;
@@ -69,12 +68,11 @@ final class AuthController extends BaseController
             ]);
         }
 
-        $antiXss = new AntiXSS();
-        $code = $antiXss->xss_clean($request->getParam('code'));
+        $mfa_code = $this->antiXss->xss_clean($request->getParam('mfa_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';
+        $email = strtolower(trim($this->antiXss->xss_clean($request->getParam('email'))));
+        $redir = $this->antiXss->xss_clean(Cookie::get('redir')) ?? '/user';
         $user = (new User())->where('email', $email)->first();
         $loginIp = new LoginIp();
 
@@ -96,7 +94,7 @@ final class AuthController extends BaseController
             ]);
         }
 
-        if ($user->ga_enable && (strlen($code) !== 6 || ! MFA::verifyGa($user, $code))) {
+        if ($user->ga_enable && (strlen($mfa_code) !== 6 || ! MFA::verifyGa($user, $mfa_code))) {
             $loginIp->collectLoginIP($_SERVER['REMOTE_ADDR'], 1, $user->id);
 
             return $response->withJson([
@@ -117,10 +115,9 @@ final class AuthController extends BaseController
         $user->last_login_time = time();
         $user->save();
 
-        return $response->withJson([
+        return $response->withHeader('HX-Redirect', $redir)->withJson([
             'ret' => 1,
             'msg' => '登录成功',
-            'redir' => $redir,
         ]);
     }
 
@@ -135,12 +132,11 @@ final class AuthController extends BaseController
             $captcha = Captcha::generate();
         }
 
-        $antiXss = new AntiXSS();
-        $code = $antiXss->xss_clean($request->getParam('code'));
+        $invite_code = $this->antiXss->xss_clean($request->getParam('code'));
 
         return $response->write(
             $this->view()
-                ->assign('code', $code)
+                ->assign('invite_code', $invite_code)
                 ->assign('base_url', $_ENV['baseUrl'])
                 ->assign('captcha', $captcha)
                 ->fetch('auth/register.tpl')
@@ -153,8 +149,7 @@ final class AuthController extends BaseController
     public function sendVerify(ServerRequest $request, Response $response, $next): Response|ResponseInterface
     {
         if (Config::obtain('reg_email_verify')) {
-            $antiXss = new AntiXSS();
-            $email = strtolower(trim($antiXss->xss_clean($request->getParam('email'))));
+            $email = strtolower(trim($this->antiXss->xss_clean($request->getParam('email'))));
 
             if ($email === '') {
                 return ResponseHelper::error($response, '未填写邮箱');
@@ -178,9 +173,9 @@ final class AuthController extends BaseController
                 return ResponseHelper::error($response, '此邮箱已经注册');
             }
 
-            $code = Tools::genRandomChar(6);
+            $email_code = Tools::genRandomChar(6);
             $redis = (new Cache())->initRedis();
-            $redis->setex('email_verify:' . $code, Config::obtain('email_verify_code_ttl'), $email);
+            $redis->setex('email_verify:' . $email_code, Config::obtain('email_verify_code_ttl'), $email);
 
             try {
                 Mail::send(
@@ -188,7 +183,7 @@ final class AuthController extends BaseController
                     $_ENV['appName'] . '- 验证邮件',
                     'verify_code.tpl',
                     [
-                        'code' => $code,
+                        'code' => $email_code,
                         'expire' => date('Y-m-d H:i:s', time() + Config::obtain('email_verify_code_ttl')),
                     ]
                 );
@@ -207,7 +202,7 @@ final class AuthController extends BaseController
      * @param $name
      * @param $email
      * @param $passwd
-     * @param $code
+     * @param $invite_code
      * @param $imtype
      * @param $imvalue
      * @param $money
@@ -217,18 +212,18 @@ final class AuthController extends BaseController
      *
      * @throws Exception
      */
-    public static function registerHelper(
+    public function registerHelper(
         Response $response,
         $name,
         $email,
         $passwd,
-        $code,
+        $invite_code,
         $imtype,
         $imvalue,
         $money,
         $is_admin_reg
     ): ResponseInterface {
-        $redir = Cookie::get('redir') ?? '/user';
+        $redir = $this->antiXss->xss_clean(Cookie::get('redir')) ?? '/user';
         $configs = Config::getClass('reg');
         // do reg user
         $user = new User();
@@ -262,8 +257,8 @@ final class AuthController extends BaseController
 
         $user->ref_by = 0;
 
-        if ($code !== '') {
-            $invite = (new InviteCode())->where('code', $code)->first();
+        if ($invite_code !== '') {
+            $invite = (new InviteCode())->where('code', $invite_code)->first();
             $invite->reward();
             $user->ref_by = $invite->user_id;
             $user->money = Config::obtain('invitation_to_register_balance_reward');
@@ -291,10 +286,9 @@ final class AuthController extends BaseController
             Auth::login($user->id, 3600);
             (new LoginIp())->collectLoginIP($_SERVER['REMOTE_ADDR'], 0, $user->id);
 
-            return $response->withJson([
+            return $response->withHeader('HX-Redirect', $redir)->withJson([
                 'ret' => 1,
                 'msg' => '注册成功!正在进入登录界面',
-                'redir' => $redir,
             ]);
         }
 
@@ -321,24 +315,23 @@ final class AuthController extends BaseController
             return ResponseHelper::error($response, '系统无法接受你的验证结果,请刷新页面后重试。');
         }
 
-        $antiXss = new AntiXSS();
         $tos = $request->getParam('tos') === 'true' ? 1 : 0;
-        $email = strtolower(trim($antiXss->xss_clean($request->getParam('email'))));
-        $name = $antiXss->xss_clean($request->getParam('name'));
+        $email = strtolower(trim($this->antiXss->xss_clean($request->getParam('email'))));
+        $name = $this->antiXss->xss_clean($request->getParam('name'));
         $passwd = $request->getParam('passwd');
         $repasswd = $request->getParam('repasswd');
-        $code = $antiXss->xss_clean(trim($request->getParam('code')));
+        $invite_code = $this->antiXss->xss_clean(trim($request->getParam('invite_code')));
         // Check TOS agreement
         if (! $tos) {
             return ResponseHelper::error($response, '请同意服务条款');
         }
         // Check Invite Code
-        if ($code === '' && Config::obtain('reg_mode') === 'invite') {
+        if ($invite_code === '' && Config::obtain('reg_mode') === 'invite') {
             return ResponseHelper::error($response, '邀请码不能为空');
         }
 
-        if ($code !== '') {
-            $user_invite = (new InviteCode())->where('code', $code)->first();
+        if ($invite_code !== '') {
+            $user_invite = (new InviteCode())->where('code', $invite_code)->first();
 
             if ($user_invite === null) {
                 return ResponseHelper::error($response, '邀请码无效');
@@ -377,7 +370,7 @@ final class AuthController extends BaseController
 
         if (Config::obtain('reg_email_verify')) {
             $redis = (new Cache())->initRedis();
-            $email_verify_code = trim($antiXss->xss_clean($request->getParam('emailcode')));
+            $email_verify_code = trim($this->antiXss->xss_clean($request->getParam('emailcode')));
             $email_verify = $redis->get('email_verify:' . $email_verify_code);
 
             if (! $email_verify) {
@@ -387,7 +380,7 @@ final class AuthController extends BaseController
             $redis->del('email_verify:' . $email_verify_code);
         }
 
-        return $this->registerHelper($response, $name, $email, $passwd, $code, $imtype, $imvalue, 0, 0);
+        return $this->registerHelper($response, $name, $email, $passwd, $invite_code, $imtype, $imvalue, 0, 0);
     }
 
     public function logout(ServerRequest $request, Response $response, $next): Response

+ 7 - 0
src/Controllers/BaseController.php

@@ -9,6 +9,7 @@ use App\Services\Auth;
 use App\Services\View;
 use Smarty;
 use Twig\Environment;
+use voku\helper\AntiXSS;
 
 abstract class BaseController
 {
@@ -27,12 +28,18 @@ abstract class BaseController
      */
     protected User $user;
 
+    /**
+     * @var AntiXSS
+     */
+    protected AntiXSS $antiXss;
+
     /**
      * Construct page renderer
      */
     public function __construct()
     {
         $this->user = Auth::getUser();
+        $this->antiXss = new AntiXSS();
     }
 
     /**

+ 1 - 4
src/Controllers/OAuthController.php

@@ -19,7 +19,6 @@ use RedisException;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
 use SmartyException;
-use voku\helper\AntiXSS;
 use function hash;
 use function hash_hmac;
 use function implode;
@@ -245,9 +244,7 @@ final class OAuthController extends BaseController
             return ResponseHelper::error($response, self::$err_msg);
         }
 
-        $antiXss = new AntiXSS();
-
-        $telegram_id = $antiXss->xss_clean($user_auth['id']);
+        $telegram_id = $this->antiXss->xss_clean($user_auth['id']);
         $user = $this->user;
 
         if ((new User())->where('im_type', 4)->where('im_value', $telegram_id)->first() !== null ||

+ 3 - 7
src/Controllers/PasswordController.php

@@ -18,7 +18,6 @@ use Psr\Http\Message\ResponseInterface;
 use RedisException;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
-use voku\helper\AntiXSS;
 use function strlen;
 
 final class PasswordController extends BaseController
@@ -54,8 +53,7 @@ final class PasswordController extends BaseController
             }
         }
 
-        $antiXss = new AntiXSS();
-        $email = strtolower($antiXss->xss_clean($request->getParam('email')));
+        $email = strtolower($this->antiXss->xss_clean($request->getParam('email')));
 
         if ($email === '') {
             return ResponseHelper::error($response, '未填写邮箱');
@@ -86,8 +84,7 @@ final class PasswordController extends BaseController
      */
     public function token(ServerRequest $request, Response $response, array $args)
     {
-        $antiXss = new AntiXSS();
-        $token = $antiXss->xss_clean($args['token']);
+        $token = $this->antiXss->xss_clean($args['token']);
         $redis = (new Cache())->initRedis();
         $email = $redis->get('password_reset:' . $token);
 
@@ -105,8 +102,7 @@ final class PasswordController extends BaseController
      */
     public function handleToken(ServerRequest $request, Response $response, array $args): ResponseInterface
     {
-        $antiXss = new AntiXSS();
-        $token = $antiXss->xss_clean($args['token']);
+        $token = $this->antiXss->xss_clean($args['token']);
         $password = $request->getParam('password');
         $repasswd = $request->getParam('repasswd');
 

+ 3 - 5
src/Controllers/SubController.php

@@ -16,7 +16,6 @@ use Psr\Http\Client\ClientExceptionInterface;
 use Psr\Http\Message\ResponseInterface;
 use RedisException;
 use Telegram\Bot\Exceptions\TelegramSDKException;
-use voku\helper\AntiXSS;
 use function in_array;
 use function strtotime;
 
@@ -34,7 +33,7 @@ final class SubController extends BaseController
      * @throws RedisException
      * @throws TelegramSDKException
      */
-    public static function getUniversalSubContent($request, $response, $args): ResponseInterface
+    public function getUniversalSubContent($request, $response, $args): ResponseInterface
     {
         $err_msg = '订阅链接无效';
 
@@ -48,8 +47,7 @@ final class SubController extends BaseController
             return ResponseHelper::error($response, $err_msg);
         }
 
-        $antiXss = new AntiXSS();
-        $token = $antiXss->xss_clean($args['token']);
+        $token = $this->antiXss->xss_clean($args['token']);
 
         if ($_ENV['enable_rate_limit'] &&
             (! RateLimit::checkIPLimit($request->getServerParam('REMOTE_ADDR')) ||
@@ -79,7 +77,7 @@ final class SubController extends BaseController
         . '; expire=' . strtotime($user->class_expire);
 
         if (Config::obtain('subscribe_log')) {
-            (new SubscribeLog())->add($user, $subtype, $antiXss->xss_clean($request->getHeaderLine('User-Agent')));
+            (new SubscribeLog())->add($user, $subtype, $this->antiXss->xss_clean($request->getHeaderLine('User-Agent')));
         }
 
         return $response->withHeader('Subscription-Userinfo', $sub_details)

+ 1 - 3
src/Controllers/User/ClientController.php

@@ -9,15 +9,13 @@ use App\Services\Cloudflare;
 use Psr\Http\Message\ResponseInterface;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
-use voku\helper\AntiXSS;
 use function in_array;
 
 final class ClientController extends BaseController
 {
     public function getClients(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {
-        $antiXss = new AntiXSS();
-        $clientName = $antiXss->xss_clean($args['name']);
+        $clientName = $this->antiXss->xss_clean($args['name']);
 
         if (! $_ENV['enable_r2_client_download'] || $clientName === '' || $clientName === null) {
             return $response->withStatus(404);

+ 2 - 4
src/Controllers/User/CouponController.php

@@ -11,7 +11,6 @@ use App\Models\UserCoupon;
 use Psr\Http\Message\ResponseInterface;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
-use voku\helper\AntiXSS;
 use function explode;
 use function in_array;
 use function json_decode;
@@ -21,9 +20,8 @@ final class CouponController extends BaseController
 {
     public function check(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {
-        $antiXss = new AntiXSS();
-        $coupon_raw = $antiXss->xss_clean($request->getParam('coupon'));
-        $product_id = $antiXss->xss_clean($request->getParam('product_id'));
+        $coupon_raw = $this->antiXss->xss_clean($request->getParam('coupon'));
+        $product_id = $this->antiXss->xss_clean($request->getParam('product_id'));
         $invalid_coupon_msg = '优惠码无效';
 
         if ($coupon_raw === '') {

+ 4 - 10
src/Controllers/User/InfoController.php

@@ -19,7 +19,6 @@ use Ramsey\Uuid\Uuid;
 use RedisException;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
-use voku\helper\AntiXSS;
 use function in_array;
 use function strlen;
 use function strtolower;
@@ -49,8 +48,7 @@ final class InfoController extends BaseController
      */
     public function updateEmail(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {
-        $antiXss = new AntiXSS();
-        $new_email = $antiXss->xss_clean($request->getParam('newemail'));
+        $new_email = $this->antiXss->xss_clean($request->getParam('newemail'));
         $user = $this->user;
         $old_email = $user->email;
 
@@ -107,8 +105,7 @@ final class InfoController extends BaseController
 
     public function updateUsername(ServerRequest $request, Response $response, array $args): ResponseInterface
     {
-        $antiXss = new AntiXSS();
-        $newusername = $antiXss->xss_clean($request->getParam('newusername'));
+        $newusername = $this->antiXss->xss_clean($request->getParam('newusername'));
         $user = $this->user;
 
         if ($user->is_shadow_banned) {
@@ -212,10 +209,8 @@ final class InfoController extends BaseController
 
     public function updateMethod(ServerRequest $request, Response $response, array $args): ResponseInterface
     {
-        $antiXss = new AntiXSS();
-
         $user = $this->user;
-        $method = strtolower($antiXss->xss_clean($request->getParam('method')));
+        $method = strtolower($this->antiXss->xss_clean($request->getParam('method')));
 
         if ($method === '') {
             ResponseHelper::error($response, '非法输入');
@@ -294,8 +289,7 @@ final class InfoController extends BaseController
 
     public function updateTheme(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {
-        $antiXss = new AntiXSS();
-        $theme = $antiXss->xss_clean($request->getParam('theme'));
+        $theme = $this->antiXss->xss_clean($request->getParam('theme'));
         $user = $this->user;
 
         if ($theme === '') {

+ 2 - 5
src/Controllers/User/InvoiceController.php

@@ -14,7 +14,6 @@ use Exception;
 use Psr\Http\Message\ResponseInterface;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
-use voku\helper\AntiXSS;
 use function json_decode;
 use function time;
 
@@ -50,8 +49,7 @@ final class InvoiceController extends BaseController
      */
     public function detail(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {
-        $antiXss = new AntiXSS();
-        $id = $antiXss->xss_clean($args['id']);
+        $id = $this->antiXss->xss_clean($args['id']);
 
         $invoice = (new Invoice())->where('user_id', $this->user->id)->where('id', $id)->first();
 
@@ -83,8 +81,7 @@ final class InvoiceController extends BaseController
 
     public function payBalance(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {
-        $antiXss = new AntiXSS();
-        $invoice_id = $antiXss->xss_clean($request->getParam('invoice_id'));
+        $invoice_id = $this->antiXss->xss_clean($request->getParam('invoice_id'));
 
         $invoice = (new Invoice())->where('user_id', $this->user->id)->where('id', $invoice_id)->first();
 

+ 1 - 3
src/Controllers/User/MoneyController.php

@@ -12,7 +12,6 @@ use Exception;
 use Psr\Http\Message\ResponseInterface;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
-use voku\helper\AntiXSS;
 use function time;
 
 final class MoneyController extends BaseController
@@ -41,8 +40,7 @@ final class MoneyController extends BaseController
 
     public function applyGiftCard(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {
-        $antiXss = new AntiXSS();
-        $giftcard_raw = $antiXss->xss_clean($request->getParam('giftcard'));
+        $giftcard_raw = $this->antiXss->xss_clean($request->getParam('giftcard'));
         $giftcard = (new GiftCard())->where('card', $giftcard_raw)->first();
 
         if ($giftcard === null || $giftcard->status !== 0) {

+ 4 - 8
src/Controllers/User/OrderController.php

@@ -15,7 +15,6 @@ use Exception;
 use Psr\Http\Message\ResponseInterface;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
-use voku\helper\AntiXSS;
 use function explode;
 use function in_array;
 use function json_decode;
@@ -59,8 +58,7 @@ final class OrderController extends BaseController
      */
     public function create(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {
-        $antiXss = new AntiXSS();
-        $product_id = $antiXss->xss_clean($request->getQueryParams()['product_id']) ?? null;
+        $product_id = $this->antiXss->xss_clean($request->getQueryParams()['product_id']) ?? null;
         $redir = Cookie::get('redir');
 
         if ($redir !== null) {
@@ -87,8 +85,7 @@ final class OrderController extends BaseController
      */
     public function detail(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {
-        $antiXss = new AntiXSS();
-        $id = $antiXss->xss_clean($args['id']);
+        $id = $this->antiXss->xss_clean($args['id']);
 
         $order = (new Order())->where('user_id', $this->user->id)->where('id', $id)->first();
 
@@ -119,9 +116,8 @@ final class OrderController extends BaseController
 
     public function process(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {
-        $antiXss = new AntiXSS();
-        $coupon_raw = $antiXss->xss_clean($request->getParam('coupon'));
-        $product_id = $antiXss->xss_clean($request->getParam('product_id'));
+        $coupon_raw = $this->antiXss->xss_clean($request->getParam('coupon'));
+        $product_id = $this->antiXss->xss_clean($request->getParam('product_id'));
 
         $product = (new Product())->find($product_id);
 

+ 4 - 9
src/Controllers/User/TicketController.php

@@ -18,7 +18,6 @@ use RedisException;
 use Slim\Http\Response;
 use Slim\Http\ServerRequest;
 use Telegram\Bot\Exceptions\TelegramSDKException;
-use voku\helper\AntiXSS;
 use function array_merge;
 use function count;
 use function json_decode;
@@ -78,24 +77,22 @@ final class TicketController extends BaseController
             ]);
         }
 
-        $antiXss = new AntiXSS();
-
         $content = [
             [
                 'comment_id' => 0,
                 'commenter_name' => $this->user->user_name,
-                'comment' => $antiXss->xss_clean($comment),
+                'comment' => $this->antiXss->xss_clean($comment),
                 'datetime' => time(),
             ],
         ];
 
         $ticket = new Ticket();
-        $ticket->title = $antiXss->xss_clean($title);
+        $ticket->title = $this->antiXss->xss_clean($title);
         $ticket->content = json_encode($content);
         $ticket->userid = $this->user->id;
         $ticket->datetime = time();
         $ticket->status = 'open_wait_admin';
-        $ticket->type = $antiXss->xss_clean($type);
+        $ticket->type = $this->antiXss->xss_clean($type);
         $ticket->save();
 
         if (Config::obtain('mail_ticket')) {
@@ -140,14 +137,12 @@ final class TicketController extends BaseController
             ]);
         }
 
-        $antiXss = new AntiXSS();
-
         $content_old = json_decode($ticket->content, true);
         $content_new = [
             [
                 'comment_id' => $content_old[count($content_old) - 1]['comment_id'] + 1,
                 'commenter_name' => $this->user->user_name,
-                'comment' => $antiXss->xss_clean($comment),
+                'comment' => $this->antiXss->xss_clean($comment),
                 'datetime' => time(),
             ],
         ];

+ 1 - 1
src/Middleware/Auth.php → src/Middleware/User.php

@@ -15,7 +15,7 @@ use function in_array;
 use function str_contains;
 use function time;
 
-final class Auth implements MiddlewareInterface
+final class User implements MiddlewareInterface
 {
     public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
     {