cloud 1 hafta önce
ebeveyn
işleme
8f952f15db

+ 0 - 2
saiadmin-artd/src/types/api/api.d.ts

@@ -86,8 +86,6 @@ declare namespace Api {
     interface LoginParams {
       username: string
       password: string
-      code: string
-      uuid: string
     }
 
     /** 登录响应 */

+ 3 - 45
saiadmin-artd/src/views/auth/login/index.vue

@@ -35,23 +35,6 @@
                 show-password
               />
             </ElFormItem>
-            <ElFormItem prop="code">
-              <ElInput
-                class="custom-height"
-                :placeholder="$t('login.placeholder.code')"
-                v-model.trim="formData.code"
-                type="text"
-                autocomplete="off"
-              >
-                <template #append>
-                  <img
-                    :src="captcha"
-                    style="height: 36px; cursor: pointer"
-                    @click="refreshCaptcha"
-                  />
-                </template>
-              </ElInput>
-            </ElFormItem>
 
             <div class="flex-cb mt-2 text-sm">
               <ElCheckbox v-model="formData.rememberPassword">{{
@@ -92,7 +75,7 @@
   import { useUserStore } from '@/store/modules/user'
   import { useI18n } from 'vue-i18n'
   import { HttpError } from '@/utils/http/error'
-  import { fetchCaptcha, fetchLogin, fetchGetUserInfo } from '@/api/auth'
+  import { fetchLogin, fetchGetUserInfo } from '@/api/auth'
   import { ElNotification, type FormInstance, type FormRules } from 'element-plus'
 
   defineOptions({ name: 'Login' })
@@ -108,33 +91,22 @@
   const userStore = useUserStore()
   const router = useRouter()
 
-  const captcha = ref(
-    'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
-  )
-
   const systemName = AppConfig.systemInfo.name
   const formRef = ref<FormInstance>()
 
   const formData = reactive({
     username: '',
     password: '',
-    code: '',
-    uuid: '',
     rememberPassword: true
   })
 
   const rules = computed<FormRules>(() => ({
     username: [{ required: true, message: t('login.placeholder.username'), trigger: 'blur' }],
-    password: [{ required: true, message: t('login.placeholder.password'), trigger: 'blur' }],
-    code: [{ required: true, message: t('login.placeholder.code'), trigger: 'blur' }]
+    password: [{ required: true, message: t('login.placeholder.password'), trigger: 'blur' }]
   }))
 
   const loading = ref(false)
 
-  onMounted(() => {
-    refreshCaptcha()
-  })
-
   // 登录
   const handleSubmit = async () => {
     if (!formRef.value) return
@@ -149,9 +121,7 @@
       // 登录请求
       const { access_token, refresh_token } = await fetchLogin({
         username: formData.username,
-        password: formData.password,
-        code: formData.code,
-        uuid: formData.uuid
+        password: formData.password
       })
 
       // 验证token
@@ -176,22 +146,10 @@
         console.error('[Login] Unexpected error:', error)
       }
     } finally {
-      refreshCaptcha()
       loading.value = false
     }
   }
 
-  // 获取验证码
-  const refreshCaptcha = async () => {
-    try {
-      const res = await fetchCaptcha()
-      formData.uuid = res.uuid
-      captcha.value = res.image
-    } catch (error) {
-      console.error('[Login] Failed to load captcha:', error)
-    }
-  }
-
   // 登录成功提示
   const showLoginSuccessNotice = () => {
     setTimeout(() => {

+ 2 - 2
server/app/miniapi/auth/TestPhoneIdentityProvider.php

@@ -30,8 +30,8 @@ final class TestPhoneIdentityProvider implements IdentityProvider
         if (!self::enabled()) {
             throw new ApiException(50301, '测试手机号登录未开启', 503);
         }
-        if (!in_array($this->phone, self::PHONES, true) || !hash_equals('66666', $code)) {
-            throw new ApiException(40102, '测试手机号或验证码错误', 401);
+        if (!in_array($this->phone, self::PHONES, true)) {
+            throw new ApiException(40102, '测试手机号错误', 401);
         }
 
         return ['appid' => self::APP_ID, 'openid' => $this->phone];

+ 2 - 3
server/app/miniapi/service/TestPhoneLogin.php

@@ -18,11 +18,10 @@ final class TestPhoneLogin
         }
         Fields::only($input, ['phone', 'code']);
         $phone = Fields::text($input, 'phone', 11);
-        $code = Fields::text($input, 'code', 5);
         if (!in_array($phone, TestPhoneIdentityProvider::PHONES, true)) {
-            throw new ApiException(40102, '测试手机号或验证码错误', 401);
+            throw new ApiException(40102, '测试手机号错误', 401);
         }
-        $identity = (new TestPhoneIdentityProvider($phone))->exchange($code);
+        $identity = (new TestPhoneIdentityProvider($phone))->exchange('');
         if (!MiniUser::query()->where($identity)->exists()) {
             throw new ApiException(50301, '测试账号尚未初始化', 503);
         }

+ 0 - 6
server/plugin/saiadmin/app/controller/LoginController.php

@@ -47,12 +47,6 @@ class LoginController extends BaseController
         $password = $request->post('password', '');
         $type = $request->post('type', 'pc');
 
-        $code = $request->post('code', '');
-        $uuid = $request->post('uuid', '');
-        $captcha = new Captcha();
-        if (!$captcha->checkCaptcha($uuid, $code)) {
-            return $this->fail('验证码错误');
-        }
         $logic = new SystemUserLogic();
         $data = $logic->login($username, $password, $type);
         return $this->success($data);

+ 4 - 4
server/tests/mini_test_phone_test.php

@@ -78,7 +78,6 @@ try {
     ]);
     reject(fn () => $service->login($input + ['user_id' => 1]), 42201);
     reject(fn () => $service->login(['phone' => '13012345681', 'code' => '66666']), 40102);
-    reject(fn () => $service->login(['phone' => '13012345678', 'code' => '66665']), 40102);
     reject(fn () => $service->login(['phone' => ['13012345678'], 'code' => '66666']), 42201);
 
     $users = [];
@@ -91,7 +90,7 @@ try {
     $count = MiniUser::query()->count();
     $sessions = [];
     foreach (TestPhoneIdentityProvider::PHONES as $role => $phone) {
-        $sessions[$role] = $service->login(['phone' => $phone, 'code' => '66666']);
+        $sessions[$role] = $service->login(['phone' => $phone]);
         check($sessions[$role]['user_id'] === (string) $users[$role]->id, 'Correct account for ' . $role);
         check(count($sessions[$role]['identities']) <= 1, 'At most one role for ' . $role);
     }
@@ -108,7 +107,7 @@ try {
         ['127.0.0.1', '198.51.100.25'],
     ] as [$source, $forwarded]) {
         foreach (TestPhoneIdentityProvider::PHONES as $role => $phone) {
-            $request = new TestPhoneRequest($source, ['phone' => $phone, 'code' => '66666'], $forwarded);
+            $request = new TestPhoneRequest($source, ['phone' => $phone], $forwarded);
             $response = $controller->testPhone($request);
             $body = json_decode($response->rawBody(), true, 512, JSON_THROW_ON_ERROR);
             check($response->getStatusCode() === 200 && $body['code'] === 0, 'HTTP test login succeeds from ' . $source);
@@ -119,7 +118,8 @@ try {
             );
         }
     }
-    reject(fn () => $controller->testPhone(new TestPhoneRequest('203.0.113.20', ['phone' => $input['phone'], 'code' => '66665'])), 40102);
+    $response = $controller->testPhone(new TestPhoneRequest('203.0.113.20', ['phone' => $input['phone'], 'code' => '66665']));
+    check($response->getStatusCode() === 200, 'Legacy clients can submit an ignored code');
     reject(fn () => $controller->testPhone(new TestPhoneRequest('203.0.113.20', ['phone' => '13800138000', 'code' => '66666'])), 40102);
     settings(['test_phone_login' => false]);
     reject(fn () => $controller->testPhone(new TestPhoneRequest('203.0.113.20', $input)), 50301);

+ 6 - 10
water-mini/src/pages/account/login.vue

@@ -138,11 +138,8 @@ async function loginByPhone() {
     toast('请输入正确的手机号码');
     return;
   }
-  const validCode = isTestAccount.value
-    ? /^\d{5}$/.test(phoneCode.value)
-    : /^\d{6}$/.test(phoneCode.value);
-  if (!validCode) {
-    toast(isTestAccount.value ? '测试账号请输入5位测试验证码 66666' : '请输入6位短信验证码');
+  if (!isTestAccount.value && !/^\d{6}$/.test(phoneCode.value)) {
+    toast('请输入6位短信验证码');
     return;
   }
   if (busy.value || !assertPhoneLoginAvailable()) return;
@@ -200,18 +197,17 @@ async function loginByPhone() {
             placeholder="请输入手机号码"
           />
         </view>
-        <view class="field code-field">
+        <view v-if="!isTestAccount" class="field code-field">
           <text class="field-label">验证码</text>
           <input
             v-model="phoneCode"
             class="input"
             type="number"
-            :maxlength="isTestAccount ? 5 : 6"
-            :placeholder="isTestAccount ? '请输入5位测试验证码' : '请输入6位短信验证码'"
+            :maxlength="6"
+            placeholder="请输入6位短信验证码"
             @confirm="loginByPhone"
           />
           <button
-            v-if="!isTestAccount"
             class="code-button"
             :loading="sendingCode"
             :disabled="sendingCode || codeCountdown > 0"
@@ -222,7 +218,7 @@ async function loginByPhone() {
             }}
           </button>
         </view>
-        <view v-if="isTestAccount" class="muted">测试账号直接输入验证码 66666,无需获取短信。</view>
+        <view v-if="isTestAccount" class="muted">测试账号输入手机号即可登录,无需验证码。</view>
         <view v-else class="muted">
           请使用后台已绑定的手机号,短信验证码两分钟内有效,过期请重新获取。
         </view>

+ 3 - 3
water-mini/src/utils/login-flow.ts

@@ -12,7 +12,7 @@ export function isTestPhone(phone: string): boolean {
 
 export async function requestPhoneCode(phone: string): Promise<boolean> {
   if (isTestPhone(phone)) {
-    toast('测试账号无需获取短信,请输入测试验证码 66666');
+    toast('测试账号无需获取短信,输入手机号即可登录');
     return false;
   }
   try {
@@ -25,6 +25,6 @@ export async function requestPhoneCode(phone: string): Promise<boolean> {
 }
 
 export async function loginByPhoneCode(phone: string, code: string): Promise<Login> {
-  const path = isTestPhone(phone) ? '/auth/test-phone' : '/auth/phone-login';
-  return post<Login>(path, { phone, code }, false);
+  if (isTestPhone(phone)) return post<Login>('/auth/test-phone', { phone }, false);
+  return post<Login>('/auth/phone-login', { phone, code }, false);
 }

+ 5 - 7
water-mini/tests/login-flow.test.mjs

@@ -98,19 +98,18 @@ test('real phone numbers request SMS and use SMS login', async () => {
   }
 });
 
-test('all three test accounts skip SMS and submit their five-digit code to test-phone', async () => {
+test('all three test accounts log in without SMS or a verification code', async () => {
   for (const phone of ['13012345678', '13012345679', '13012345680']) {
     const app = fixture();
     assert.equal(await app.requestPhoneCode(phone), false);
     assert.equal(app.posts.length, 0);
     assert.match(app.messages.at(-1), /无需获取短信/);
     app.state.phone.value = phone;
-    app.state.phoneCode.value = '66666';
     app.state.agreed.value = true;
     const login = app.state.loginByPhone();
     assert.equal(app.posts.length, 1);
     assert.equal(app.posts[0].path, '/auth/test-phone');
-    assert.deepEqual(app.posts[0].payload, { phone, code: '66666' });
+    assert.deepEqual(app.posts[0].payload, { phone });
     assert.equal(app.posts[0].authenticated, false);
     app.posts[0].resolve({ user_id: '42' });
     await login;
@@ -225,17 +224,16 @@ test('real login requires agreement, forwards the code and serializes taps', asy
   assert.equal(app.state.busy.value, false);
 });
 
-test('code length follows the account type and fixed code cannot log into a real number', async () => {
+test('real phone numbers still require a six-digit SMS code', async () => {
   const app = fixture();
   app.state.agreed.value = true;
   app.state.phone.value = '13800138000';
   app.state.phoneCode.value = '66666';
   await app.state.loginByPhone();
-  app.state.phone.value = '13012345678';
-  app.state.phoneCode.value = '123456';
+  app.state.phoneCode.value = '';
   await app.state.loginByPhone();
   assert.equal(app.posts.length, 0);
-  assert.match(app.messages.at(-1), /5位测试验证码/);
+  assert.match(app.messages.at(-1), /6位短信验证码/);
 });
 
 test('wrong code is reported without accepting a session or navigating away', async () => {