add captcha

This commit is contained in:
lejianwen
2024-12-18 12:42:55 +08:00
parent 8ecfe9cbd0
commit f20003e619
9 changed files with 157 additions and 102 deletions
+10 -3
View File
@@ -1,6 +1,6 @@
import request from '@/utils/request'; import request from '@/utils/request'
export function loginOptions() { export function loginOptions () {
return request({ return request({
url: '/login-options', url: '/login-options',
method: 'get', method: 'get',
@@ -15,10 +15,17 @@ export function oidcAuth (data) {
}) })
} }
export function oidcQuery(params){ export function oidcQuery (params) {
return request({ return request({
url: '/oidc/auth-query', url: '/oidc/auth-query',
method: 'get', method: 'get',
params, params,
}) })
} }
export function captcha () {
return request({
url: '/captcha',
method: 'get',
})
}
+4 -3
View File
@@ -41,14 +41,15 @@ export const useUserStore = defineStore({
}, },
async login (form) { async login (form) {
const res = await login(form).catch(_ => false) const res = await login(form).catch(e => e)
if (res) { console.log('login', res)
if (!res.code) {
useAppStore().loadConfig() useAppStore().loadConfig()
const userData = res.data const userData = res.data
this.saveUserData(userData) this.saveUserData(userData)
return userData return userData
} else { } else {
return false return Promise.reject(res)
} }
}, },
async info () { async info () {
+3
View File
@@ -465,5 +465,8 @@
}, },
"Second": { "Second": {
"One": "Second" "One": "Second"
},
"Captcha": {
"One": "Captcha"
} }
} }
+3
View File
@@ -468,5 +468,8 @@
}, },
"Second": { "Second": {
"One": "Segundo" "One": "Segundo"
},
"Captcha": {
"One": "Captcha"
} }
} }
+3
View File
@@ -454,5 +454,8 @@
}, },
"Second": { "Second": {
"One": "초" "One": "초"
},
"Captcha": {
"One": "Captcha"
} }
} }
+3
View File
@@ -468,6 +468,9 @@
}, },
"Second": { "Second": {
"One": "Секунда" "One": "Секунда"
},
"Captcha": {
"One": "Captcha"
} }
} }
+3
View File
@@ -484,5 +484,8 @@
}, },
"Second": { "Second": {
"One": "秒" "One": "秒"
},
"Captcha": {
"One": "验证码"
} }
} }
+1 -1
View File
@@ -73,7 +73,7 @@ service.interceptors.response.use(
removeToken() removeToken()
window.location.reload() window.location.reload()
} }
return Promise.reject(res.message || 'error') return Promise.reject(res)
} else { } else {
return res return res
} }
+96 -64
View File
@@ -1,7 +1,7 @@
<template> <template>
<div class="login-container"> <div class="login-container">
<div class="login-card"> <div class="login-card">
<img src="@/assets/logo.png" alt="logo" class="login-logo" /> <img src="@/assets/logo.png" alt="logo" class="login-logo"/>
<el-form label-position="top" class="login-form"> <el-form label-position="top" class="login-form">
<el-form-item :label="T('Username')"> <el-form-item :label="T('Username')">
@@ -12,7 +12,13 @@
<el-input v-model="form.password" type="password" @keyup.enter.native="login" show-password <el-input v-model="form.password" type="password" @keyup.enter.native="login" show-password
class="login-input"></el-input> class="login-input"></el-input>
</el-form-item> </el-form-item>
<el-form-item :label="T('Captcha')" v-if="captchaCode">
<el-input v-model="form.captcha" @keyup.enter.native="login" class="login-input captcha-input">
<template #append>
<img :src="captchaCode.b64" @click="loadCaptcha" class="captcha" alt="captcha"/>
</template>
</el-input>
</el-form-item>
<el-form-item> <el-form-item>
<el-button @click="login" type="primary" class="login-button">{{ T('Login') }}</el-button> <el-button @click="login" type="primary" class="login-button">{{ T('Login') }}</el-button>
<el-button v-if="allowRegister" @click="register" class="login-button">{{ T('Register') }}</el-button> <el-button v-if="allowRegister" @click="register" class="login-button">{{ T('Register') }}</el-button>
@@ -26,7 +32,7 @@
<div class="oidc-options"> <div class="oidc-options">
<div v-for="(option, index) in options" :key="index" class="oidc-option"> <div v-for="(option, index) in options" :key="index" class="oidc-option">
<el-button @click="handleOIDCLogin(option.name)" class="oidc-btn"> <el-button @click="handleOIDCLogin(option.name)" class="oidc-btn">
<img :src="getProviderImage(option.name)" alt="provider" class="oidc-icon" /> <img :src="getProviderImage(option.name)" alt="provider" class="oidc-icon"/>
<span>{{ T(option.name) }}</span> <span>{{ T(option.name) }}</span>
</el-button> </el-button>
</div> </div>
@@ -36,88 +42,103 @@
</template> </template>
<script setup> <script setup>
import { reactive, onMounted, ref } from 'vue'; import { reactive, onMounted, ref } from 'vue'
import { useUserStore } from '@/store/user' import { useUserStore } from '@/store/user'
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus'
import { T } from '@/utils/i18n'; import { T } from '@/utils/i18n'
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router'
import { loginOptions } from '@/api/login'; import { loginOptions, captcha } from '@/api/login'
import { getCode, removeCode } from '@/utils/auth' import { getCode, removeCode } from '@/utils/auth'
const oauthInfo = ref({}) const oauthInfo = ref({})
const userStore = useUserStore() const userStore = useUserStore()
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const options = reactive([]); // 存储 OIDC 登录选项 const options = reactive([]) // 存储 OIDC 登录选项
let platform = window.navigator.platform let platform = window.navigator.platform
if (navigator.platform.indexOf('Mac') === 0) { if (navigator.platform.indexOf('Mac') === 0) {
platform = 'mac' platform = 'mac'
} else if (navigator.platform.indexOf('Win') === 0) { } else if (navigator.platform.indexOf('Win') === 0) {
platform = 'windows' platform = 'windows'
} else if (navigator.platform.indexOf('Linux armv') === 0) { } else if (navigator.platform.indexOf('Linux armv') === 0) {
platform = 'android' platform = 'android'
} else if (navigator.platform.indexOf('Linux') === 0) { } else if (navigator.platform.indexOf('Linux') === 0) {
platform = 'linux' platform = 'linux'
} }
const userAgent = navigator.userAgent; const userAgent = navigator.userAgent
let browser = 'Unknown Browser'; let browser = 'Unknown Browser'
if (/chrome|crios/i.test(userAgent)) browser = 'Chrome'; if (/chrome|crios/i.test(userAgent)) browser = 'Chrome'
else if (/firefox|fxios/i.test(userAgent)) browser = 'Firefox'; else if (/firefox|fxios/i.test(userAgent)) browser = 'Firefox'
else if (/safari/i.test(userAgent) && !/chrome/i.test(userAgent)) browser = 'Safari'; else if (/safari/i.test(userAgent) && !/chrome/i.test(userAgent)) browser = 'Safari'
else if (/edg/i.test(userAgent)) browser = 'Edge'; else if (/edg/i.test(userAgent)) browser = 'Edge'
const form = reactive({ const form = reactive({
username: '', username: '',
password: '', password: '',
platform: platform, platform: platform,
}) captcha: '',
})
const redirect = route.query?.redirect const captchaCode = ref('')
const login = async () => { const redirect = route.query?.redirect
const res = await userStore.login(form) const login = async () => {
if (res) { const res = await userStore.login(form).catch(e => e)
if (!res.code) {
ElMessage.success(T('LoginSuccess')) ElMessage.success(T('LoginSuccess'))
router.push({ path: redirect || '/', replace: true }) router.push({ path: redirect || '/', replace: true })
} }
} if (res.code === 110 && captchaCode.value === '') {
// need captcha
loadCaptcha()
}
}
const handleOIDCLogin = (provider) => { const loadCaptcha = async () => {
const captchaRes = await captcha().catch(_ => false)
console.log(captchaRes)
captchaCode.value = captchaRes.data.captcha
}
const handleOIDCLogin = (provider) => {
userStore.oidc(provider, platform, browser) userStore.oidc(provider, platform, browser)
}; }
import googleImage from '@/assets/google.png'; import googleImage from '@/assets/google.png'
import githubImage from '@/assets/github.png'; import githubImage from '@/assets/github.png'
import oidcImage from '@/assets/oidc.png'; import oidcImage from '@/assets/oidc.png'
import webauthImage from '@/assets/webauth.png'; import webauthImage from '@/assets/webauth.png'
import defaultImage from '@/assets/oidc.png'; import defaultImage from '@/assets/oidc.png'
const providerImageMap = { const providerImageMap = {
google: googleImage, google: googleImage,
github: githubImage, github: githubImage,
oidc: oidcImage, oidc: oidcImage,
// WebAuth: webauthImage, // WebAuth: webauthImage,
default: defaultImage, default: defaultImage,
};
const getProviderImage = (provider) => {
return providerImageMap[provider.toLowerCase()] || providerImageMap.default;
};
const allowRegister = ref(false)
const loadLoginOptions = async () => {
try {
const res = await loginOptions().catch(_ => false);
if(!res || !res.data) return console.error('No valid response received');
res.data.ops.map(option => (options.push({ name: option }))); // 创建新的对象数组
allowRegister.value = res.data.register
} catch (error) {
console.error('Error loading login options:', error.message);
} }
};
onMounted(async () => { const getProviderImage = (provider) => {
const code = getCode(); return providerImageMap[provider.toLowerCase()] || providerImageMap.default
}
const allowRegister = ref(false)
const loadLoginOptions = async () => {
try {
const res = await loginOptions().catch(_ => false)
if (!res || !res.data) return console.error('No valid response received')
res.data.ops.map(option => (options.push({ name: option }))) // 创建新的对象数组
allowRegister.value = res.data.register
if (res.data.need_captcha) {
loadCaptcha()
}
} catch (error) {
console.error('Error loading login options:', error.message)
}
}
onMounted(async () => {
const code = getCode()
if (code) { if (code) {
// 如果code存在,进行query获取user info // 如果code存在,进行query获取user info
const res = await userStore.query(code) const res = await userStore.query(code)
@@ -129,13 +150,13 @@ onMounted(async () => {
} }
} else { } else {
// 如果code不存在, 现实登陆页面 // 如果code不存在, 现实登陆页面
loadLoginOptions(); // 组件挂载后调用登录选项加载函数 loadLoginOptions() // 组件挂载后调用登录选项加载函数
} }
}); })
const register = () => { const register = () => {
router.push('/register') router.push('/register')
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -170,6 +191,17 @@ h1 {
.login-input { .login-input {
width: 100%; width: 100%;
.captcha{
cursor: pointer;
width: 150px;
}
}
.captcha-input{
:deep(.el-input-group__append) {
border-radius: 5px;
padding: 0;
overflow: hidden;
}
} }
.login-button { .login-button {