add captcha
This commit is contained in:
+10
-3
@@ -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
@@ -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 () {
|
||||||
|
|||||||
@@ -465,5 +465,8 @@
|
|||||||
},
|
},
|
||||||
"Second": {
|
"Second": {
|
||||||
"One": "Second"
|
"One": "Second"
|
||||||
|
},
|
||||||
|
"Captcha": {
|
||||||
|
"One": "Captcha"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -468,5 +468,8 @@
|
|||||||
},
|
},
|
||||||
"Second": {
|
"Second": {
|
||||||
"One": "Segundo"
|
"One": "Segundo"
|
||||||
|
},
|
||||||
|
"Captcha": {
|
||||||
|
"One": "Captcha"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -454,5 +454,8 @@
|
|||||||
},
|
},
|
||||||
"Second": {
|
"Second": {
|
||||||
"One": "초"
|
"One": "초"
|
||||||
|
},
|
||||||
|
"Captcha": {
|
||||||
|
"One": "Captcha"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -468,6 +468,9 @@
|
|||||||
},
|
},
|
||||||
"Second": {
|
"Second": {
|
||||||
"One": "Секунда"
|
"One": "Секунда"
|
||||||
|
},
|
||||||
|
"Captcha": {
|
||||||
|
"One": "Captcha"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -484,5 +484,8 @@
|
|||||||
},
|
},
|
||||||
"Second": {
|
"Second": {
|
||||||
"One": "秒"
|
"One": "秒"
|
||||||
|
},
|
||||||
|
"Captcha": {
|
||||||
|
"One": "验证码"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
+126
-94
@@ -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')">
|
||||||
@@ -10,9 +10,15 @@
|
|||||||
|
|
||||||
<el-form-item :label="T('Password')">
|
<el-form-item :label="T('Password')">
|
||||||
<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 :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-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,106 +42,121 @@
|
|||||||
</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;
|
|
||||||
let browser = 'Unknown Browser';
|
|
||||||
if (/chrome|crios/i.test(userAgent)) browser = 'Chrome';
|
|
||||||
else if (/firefox|fxios/i.test(userAgent)) browser = 'Firefox';
|
|
||||||
else if (/safari/i.test(userAgent) && !/chrome/i.test(userAgent)) browser = 'Safari';
|
|
||||||
else if (/edg/i.test(userAgent)) browser = 'Edge';
|
|
||||||
|
|
||||||
const form = reactive({
|
|
||||||
username: '',
|
|
||||||
password: '',
|
|
||||||
platform: platform,
|
|
||||||
})
|
|
||||||
|
|
||||||
const redirect = route.query?.redirect
|
|
||||||
const login = async () => {
|
|
||||||
const res = await userStore.login(form)
|
|
||||||
if (res) {
|
|
||||||
ElMessage.success(T('LoginSuccess'))
|
|
||||||
router.push({ path: redirect || '/', replace: true })
|
|
||||||
}
|
}
|
||||||
}
|
const userAgent = navigator.userAgent
|
||||||
|
let browser = 'Unknown Browser'
|
||||||
|
if (/chrome|crios/i.test(userAgent)) browser = 'Chrome'
|
||||||
|
else if (/firefox|fxios/i.test(userAgent)) browser = 'Firefox'
|
||||||
|
else if (/safari/i.test(userAgent) && !/chrome/i.test(userAgent)) browser = 'Safari'
|
||||||
|
else if (/edg/i.test(userAgent)) browser = 'Edge'
|
||||||
|
|
||||||
const handleOIDCLogin = (provider) => {
|
const form = reactive({
|
||||||
userStore.oidc(provider, platform, browser)
|
username: '',
|
||||||
};
|
password: '',
|
||||||
|
platform: platform,
|
||||||
|
captcha: '',
|
||||||
|
})
|
||||||
|
|
||||||
import googleImage from '@/assets/google.png';
|
const captchaCode = ref('')
|
||||||
import githubImage from '@/assets/github.png';
|
const redirect = route.query?.redirect
|
||||||
import oidcImage from '@/assets/oidc.png';
|
const login = async () => {
|
||||||
import webauthImage from '@/assets/webauth.png';
|
const res = await userStore.login(form).catch(e => e)
|
||||||
import defaultImage from '@/assets/oidc.png';
|
if (!res.code) {
|
||||||
|
|
||||||
const providerImageMap = {
|
|
||||||
google: googleImage,
|
|
||||||
github: githubImage,
|
|
||||||
oidc: oidcImage,
|
|
||||||
// WebAuth: webauthImage,
|
|
||||||
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 code = getCode();
|
|
||||||
if (code) {
|
|
||||||
// 如果code存在,进行query获取user info
|
|
||||||
const res = await userStore.query(code)
|
|
||||||
if (res) {
|
|
||||||
// 删除code,确保跳转之前对code进行清楚
|
|
||||||
removeCode()
|
|
||||||
ElMessage.success(T('LoginSuccess'))
|
ElMessage.success(T('LoginSuccess'))
|
||||||
router.push({ path: redirect || '/', replace: true })
|
router.push({ path: redirect || '/', replace: true })
|
||||||
}
|
}
|
||||||
} else {
|
if (res.code === 110 && captchaCode.value === '') {
|
||||||
// 如果code不存在, 现实登陆页面
|
// need captcha
|
||||||
loadLoginOptions(); // 组件挂载后调用登录选项加载函数
|
loadCaptcha()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
const register = () => {
|
const loadCaptcha = async () => {
|
||||||
router.push('/register')
|
const captchaRes = await captcha().catch(_ => false)
|
||||||
}
|
console.log(captchaRes)
|
||||||
|
captchaCode.value = captchaRes.data.captcha
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOIDCLogin = (provider) => {
|
||||||
|
userStore.oidc(provider, platform, browser)
|
||||||
|
}
|
||||||
|
|
||||||
|
import googleImage from '@/assets/google.png'
|
||||||
|
import githubImage from '@/assets/github.png'
|
||||||
|
import oidcImage from '@/assets/oidc.png'
|
||||||
|
import webauthImage from '@/assets/webauth.png'
|
||||||
|
import defaultImage from '@/assets/oidc.png'
|
||||||
|
|
||||||
|
const providerImageMap = {
|
||||||
|
google: googleImage,
|
||||||
|
github: githubImage,
|
||||||
|
oidc: oidcImage,
|
||||||
|
// WebAuth: webauthImage,
|
||||||
|
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
|
||||||
|
if (res.data.need_captcha) {
|
||||||
|
loadCaptcha()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading login options:', error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const code = getCode()
|
||||||
|
if (code) {
|
||||||
|
// 如果code存在,进行query获取user info
|
||||||
|
const res = await userStore.query(code)
|
||||||
|
if (res) {
|
||||||
|
// 删除code,确保跳转之前对code进行清楚
|
||||||
|
removeCode()
|
||||||
|
ElMessage.success(T('LoginSuccess'))
|
||||||
|
router.push({ path: redirect || '/', replace: true })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果code不存在, 现实登陆页面
|
||||||
|
loadLoginOptions() // 组件挂载后调用登录选项加载函数
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const 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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user