lejianwen 1 год назад
Родитель
Сommit
f20003e619

+ 11 - 4
src/api/login.js

@@ -1,6 +1,6 @@
1
-import request from '@/utils/request';
1
+import request from '@/utils/request'
2
 
2
 
3
-export function loginOptions() {
3
+export function loginOptions () {
4
   return request({
4
   return request({
5
     url: '/login-options',
5
     url: '/login-options',
6
     method: 'get',
6
     method: 'get',
@@ -15,10 +15,17 @@ export function oidcAuth (data) {
15
   })
15
   })
16
 }
16
 }
17
 
17
 
18
-export function oidcQuery(params){
18
+export function oidcQuery (params) {
19
   return request({
19
   return request({
20
     url: '/oidc/auth-query',
20
     url: '/oidc/auth-query',
21
     method: 'get',
21
     method: 'get',
22
     params,
22
     params,
23
   })
23
   })
24
-}
24
+}
25
+
26
+export function captcha () {
27
+  return request({
28
+    url: '/captcha',
29
+    method: 'get',
30
+  })
31
+}

+ 4 - 3
src/store/user.js

@@ -41,14 +41,15 @@ export const useUserStore = defineStore({
41
     },
41
     },
42
 
42
 
43
     async login (form) {
43
     async login (form) {
44
-      const res = await login(form).catch(_ => false)
45
-      if (res) {
44
+      const res = await login(form).catch(e => e)
45
+      console.log('login', res)
46
+      if (!res.code) {
46
         useAppStore().loadConfig()
47
         useAppStore().loadConfig()
47
         const userData = res.data
48
         const userData = res.data
48
         this.saveUserData(userData)
49
         this.saveUserData(userData)
49
         return userData
50
         return userData
50
       } else {
51
       } else {
51
-        return false
52
+        return Promise.reject(res)
52
       }
53
       }
53
     },
54
     },
54
     async info () {
55
     async info () {

+ 3 - 0
src/utils/i18n/en.json

@@ -465,5 +465,8 @@
465
   },
465
   },
466
   "Second": {
466
   "Second": {
467
     "One": "Second"
467
     "One": "Second"
468
+  },
469
+  "Captcha": {
470
+    "One": "Captcha"
468
   }
471
   }
469
 }
472
 }

+ 3 - 0
src/utils/i18n/es.json

@@ -468,5 +468,8 @@
468
   },
468
   },
469
   "Second": {
469
   "Second": {
470
     "One": "Segundo"
470
     "One": "Segundo"
471
+  },
472
+  "Captcha": {
473
+    "One": "Captcha"
471
   }
474
   }
472
 }
475
 }

+ 3 - 0
src/utils/i18n/ko.json

@@ -454,5 +454,8 @@
454
   },
454
   },
455
   "Second": {
455
   "Second": {
456
     "One": "초"
456
     "One": "초"
457
+  },
458
+  "Captcha": {
459
+    "One": "Captcha"
457
   }
460
   }
458
 }
461
 }

+ 3 - 0
src/utils/i18n/ru.json

@@ -468,6 +468,9 @@
468
   },
468
   },
469
   "Second": {
469
   "Second": {
470
     "One": "Секунда"
470
     "One": "Секунда"
471
+  },
472
+  "Captcha": {
473
+    "One": "Captcha"
471
   }
474
   }
472
 }
475
 }
473
 
476
 

+ 3 - 0
src/utils/i18n/zh_CN.json

@@ -484,5 +484,8 @@
484
   },
484
   },
485
   "Second": {
485
   "Second": {
486
     "One": "秒"
486
     "One": "秒"
487
+  },
488
+  "Captcha": {
489
+    "One": "验证码"
487
   }
490
   }
488
 }
491
 }

+ 1 - 1
src/utils/request.js

@@ -73,7 +73,7 @@ service.interceptors.response.use(
73
         removeToken()
73
         removeToken()
74
         window.location.reload()
74
         window.location.reload()
75
       }
75
       }
76
-      return Promise.reject(res.message || 'error')
76
+      return Promise.reject(res)
77
     } else {
77
     } else {
78
       return res
78
       return res
79
     }
79
     }

+ 130 - 98
src/views/login/login.vue

@@ -1,7 +1,7 @@
1
 <template>
1
 <template>
2
   <div class="login-container">
2
   <div class="login-container">
3
     <div class="login-card">
3
     <div class="login-card">
4
-      <img src="@/assets/logo.png" alt="logo" class="login-logo" />
4
+      <img src="@/assets/logo.png" alt="logo" class="login-logo"/>
5
 
5
 
6
       <el-form label-position="top" class="login-form">
6
       <el-form label-position="top" class="login-form">
7
         <el-form-item :label="T('Username')">
7
         <el-form-item :label="T('Username')">
@@ -10,9 +10,15 @@
10
 
10
 
11
         <el-form-item :label="T('Password')">
11
         <el-form-item :label="T('Password')">
12
           <el-input v-model="form.password" type="password" @keyup.enter.native="login" show-password
12
           <el-input v-model="form.password" type="password" @keyup.enter.native="login" show-password
13
-            class="login-input"></el-input>
13
+                    class="login-input"></el-input>
14
+        </el-form-item>
15
+        <el-form-item :label="T('Captcha')" v-if="captchaCode">
16
+          <el-input v-model="form.captcha" @keyup.enter.native="login"  class="login-input captcha-input">
17
+            <template #append>
18
+              <img :src="captchaCode.b64" @click="loadCaptcha" class="captcha" alt="captcha"/>
19
+            </template>
20
+          </el-input>
14
         </el-form-item>
21
         </el-form-item>
15
-
16
         <el-form-item>
22
         <el-form-item>
17
           <el-button @click="login" type="primary" class="login-button">{{ T('Login') }}</el-button>
23
           <el-button @click="login" type="primary" class="login-button">{{ T('Login') }}</el-button>
18
           <el-button v-if="allowRegister" @click="register" class="login-button">{{ T('Register') }}</el-button>
24
           <el-button v-if="allowRegister" @click="register" class="login-button">{{ T('Register') }}</el-button>
@@ -26,7 +32,7 @@
26
       <div class="oidc-options">
32
       <div class="oidc-options">
27
         <div v-for="(option, index) in options" :key="index" class="oidc-option">
33
         <div v-for="(option, index) in options" :key="index" class="oidc-option">
28
           <el-button @click="handleOIDCLogin(option.name)" class="oidc-btn">
34
           <el-button @click="handleOIDCLogin(option.name)" class="oidc-btn">
29
-            <img :src="getProviderImage(option.name)" alt="provider" class="oidc-icon" />
35
+            <img :src="getProviderImage(option.name)" alt="provider" class="oidc-icon"/>
30
             <span>{{ T(option.name) }}</span>
36
             <span>{{ T(option.name) }}</span>
31
           </el-button>
37
           </el-button>
32
         </div>
38
         </div>
@@ -36,106 +42,121 @@
36
 </template>
42
 </template>
37
 
43
 
38
 <script setup>
44
 <script setup>
39
-import { reactive, onMounted, ref } from 'vue';
40
-import { useUserStore } from '@/store/user'
41
-import { ElMessage } from 'element-plus';
42
-import { T } from '@/utils/i18n';
43
-import { useRoute, useRouter } from 'vue-router';
44
-import { loginOptions } from '@/api/login';
45
-import { getCode, removeCode } from '@/utils/auth'
46
-
47
-const oauthInfo = ref({})
48
-const userStore = useUserStore()
49
-const route = useRoute()
50
-const router = useRouter()
51
-const options = reactive([]); // 存储 OIDC 登录选项
52
-
53
-let platform = window.navigator.platform
54
-if (navigator.platform.indexOf('Mac') === 0) {
55
-  platform = 'mac'
56
-} else if (navigator.platform.indexOf('Win') === 0) {
57
-  platform = 'windows'
58
-} else if (navigator.platform.indexOf('Linux armv') === 0) {
59
-  platform = 'android'
60
-} else if (navigator.platform.indexOf('Linux') === 0) {
61
-  platform = 'linux'
62
-}
63
-const userAgent = navigator.userAgent;
64
-let browser = 'Unknown Browser';
65
-if (/chrome|crios/i.test(userAgent)) browser = 'Chrome';
66
-else if (/firefox|fxios/i.test(userAgent)) browser = 'Firefox';
67
-else if (/safari/i.test(userAgent) && !/chrome/i.test(userAgent)) browser = 'Safari';
68
-else if (/edg/i.test(userAgent)) browser = 'Edge';
69
-
70
-const form = reactive({
71
-  username: '',
72
-  password: '',
73
-  platform: platform,
74
-})
75
-
76
-const redirect = route.query?.redirect
77
-const login = async () => {
78
-  const res = await userStore.login(form)
79
-  if (res) {
80
-    ElMessage.success(T('LoginSuccess'))
81
-    router.push({ path: redirect || '/', replace: true })
45
+  import { reactive, onMounted, ref } from 'vue'
46
+  import { useUserStore } from '@/store/user'
47
+  import { ElMessage } from 'element-plus'
48
+  import { T } from '@/utils/i18n'
49
+  import { useRoute, useRouter } from 'vue-router'
50
+  import { loginOptions, captcha } from '@/api/login'
51
+  import { getCode, removeCode } from '@/utils/auth'
52
+
53
+  const oauthInfo = ref({})
54
+  const userStore = useUserStore()
55
+  const route = useRoute()
56
+  const router = useRouter()
57
+  const options = reactive([]) // 存储 OIDC 登录选项
58
+
59
+  let platform = window.navigator.platform
60
+  if (navigator.platform.indexOf('Mac') === 0) {
61
+    platform = 'mac'
62
+  } else if (navigator.platform.indexOf('Win') === 0) {
63
+    platform = 'windows'
64
+  } else if (navigator.platform.indexOf('Linux armv') === 0) {
65
+    platform = 'android'
66
+  } else if (navigator.platform.indexOf('Linux') === 0) {
67
+    platform = 'linux'
82
   }
68
   }
83
-}
84
-
85
-const handleOIDCLogin = (provider) => {
86
-  userStore.oidc(provider, platform, browser)
87
-};
88
-
89
-import googleImage from '@/assets/google.png';
90
-import githubImage from '@/assets/github.png';
91
-import oidcImage from '@/assets/oidc.png';
92
-import webauthImage from '@/assets/webauth.png';
93
-import defaultImage from '@/assets/oidc.png';
94
-
95
-const providerImageMap = {
96
-  google: googleImage,
97
-  github: githubImage,
98
-  oidc: oidcImage,
99
-  // WebAuth: webauthImage,
100
-  default: defaultImage,
101
-};
102
-
103
-const getProviderImage = (provider) => {
104
-  return providerImageMap[provider.toLowerCase()] || providerImageMap.default;
105
-};
106
-
107
-const allowRegister = ref(false)
108
-const loadLoginOptions = async () => {
109
-  try {
110
-    const res = await loginOptions().catch(_ => false);
111
-    if(!res || !res.data) return console.error('No valid response received');
112
-    res.data.ops.map(option => (options.push({ name: option }))); // 创建新的对象数组
113
-    allowRegister.value = res.data.register
114
-  } catch (error) {
115
-    console.error('Error loading login options:', error.message);
116
-  }
117
-};
118
-
119
-onMounted(async () => {
120
-  const code = getCode();
121
-  if (code) {
122
-    // 如果code存在,进行query获取user info
123
-    const res = await userStore.query(code)
124
-    if (res) {
125
-      // 删除code,确保跳转之前对code进行清楚
126
-      removeCode()
69
+  const userAgent = navigator.userAgent
70
+  let browser = 'Unknown Browser'
71
+  if (/chrome|crios/i.test(userAgent)) browser = 'Chrome'
72
+  else if (/firefox|fxios/i.test(userAgent)) browser = 'Firefox'
73
+  else if (/safari/i.test(userAgent) && !/chrome/i.test(userAgent)) browser = 'Safari'
74
+  else if (/edg/i.test(userAgent)) browser = 'Edge'
75
+
76
+  const form = reactive({
77
+    username: '',
78
+    password: '',
79
+    platform: platform,
80
+    captcha: '',
81
+  })
82
+
83
+  const captchaCode = ref('')
84
+  const redirect = route.query?.redirect
85
+  const login = async () => {
86
+    const res = await userStore.login(form).catch(e => e)
87
+    if (!res.code) {
127
       ElMessage.success(T('LoginSuccess'))
88
       ElMessage.success(T('LoginSuccess'))
128
       router.push({ path: redirect || '/', replace: true })
89
       router.push({ path: redirect || '/', replace: true })
129
     }
90
     }
130
-  } else {
131
-    // 如果code不存在, 现实登陆页面
132
-    loadLoginOptions(); // 组件挂载后调用登录选项加载函数
91
+    if (res.code === 110 && captchaCode.value === '') {
92
+      // need captcha
93
+      loadCaptcha()
94
+    }
133
   }
95
   }
134
-});
135
 
96
 
136
-const register = () => {
137
-  router.push('/register')
138
-}
97
+  const loadCaptcha = async () => {
98
+    const captchaRes = await captcha().catch(_ => false)
99
+    console.log(captchaRes)
100
+    captchaCode.value = captchaRes.data.captcha
101
+  }
102
+
103
+  const handleOIDCLogin = (provider) => {
104
+    userStore.oidc(provider, platform, browser)
105
+  }
106
+
107
+  import googleImage from '@/assets/google.png'
108
+  import githubImage from '@/assets/github.png'
109
+  import oidcImage from '@/assets/oidc.png'
110
+  import webauthImage from '@/assets/webauth.png'
111
+  import defaultImage from '@/assets/oidc.png'
112
+
113
+  const providerImageMap = {
114
+    google: googleImage,
115
+    github: githubImage,
116
+    oidc: oidcImage,
117
+    // WebAuth: webauthImage,
118
+    default: defaultImage,
119
+  }
120
+
121
+  const getProviderImage = (provider) => {
122
+    return providerImageMap[provider.toLowerCase()] || providerImageMap.default
123
+  }
124
+
125
+  const allowRegister = ref(false)
126
+  const loadLoginOptions = async () => {
127
+    try {
128
+      const res = await loginOptions().catch(_ => false)
129
+      if (!res || !res.data) return console.error('No valid response received')
130
+      res.data.ops.map(option => (options.push({ name: option }))) // 创建新的对象数组
131
+      allowRegister.value = res.data.register
132
+      if (res.data.need_captcha) {
133
+        loadCaptcha()
134
+      }
135
+    } catch (error) {
136
+      console.error('Error loading login options:', error.message)
137
+    }
138
+  }
139
+
140
+  onMounted(async () => {
141
+    const code = getCode()
142
+    if (code) {
143
+      // 如果code存在,进行query获取user info
144
+      const res = await userStore.query(code)
145
+      if (res) {
146
+        // 删除code,确保跳转之前对code进行清楚
147
+        removeCode()
148
+        ElMessage.success(T('LoginSuccess'))
149
+        router.push({ path: redirect || '/', replace: true })
150
+      }
151
+    } else {
152
+      // 如果code不存在, 现实登陆页面
153
+      loadLoginOptions() // 组件挂载后调用登录选项加载函数
154
+    }
155
+  })
156
+
157
+  const register = () => {
158
+    router.push('/register')
159
+  }
139
 </script>
160
 </script>
140
 
161
 
141
 <style scoped lang="scss">
162
 <style scoped lang="scss">
@@ -170,6 +191,17 @@ h1 {
170
 
191
 
171
 .login-input {
192
 .login-input {
172
   width: 100%;
193
   width: 100%;
194
+  .captcha{
195
+    cursor: pointer;
196
+    width: 150px;
197
+  }
198
+}
199
+.captcha-input{
200
+  :deep(.el-input-group__append) {
201
+    border-radius: 5px;
202
+    padding: 0;
203
+    overflow: hidden;
204
+  }
173
 }
205
 }
174
 
206
 
175
 .login-button {
207
 .login-button {