添加二次认证开关

This commit is contained in:
lijun 2025-10-18 14:48:04 +08:00
parent e9ef776c76
commit fabf44d582
3 changed files with 248 additions and 198 deletions

View File

@ -49,6 +49,10 @@ export interface UserInfo {
Gender: string;
Role: string;
Avatar: string;
password_need_second_auth: number; // 是否需要密码二次认证
code_need_second_auth: number; // 是否需要验证码二次认证
third_party_need_second_auth: number; // 是否需要第三方二次认证
ai_second_auth: number; // 是否需要智能增强认证
}

View File

@ -17,11 +17,7 @@
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
type="password"
placeholder="密码"
v-model="param.password"
>
<el-input type="password" placeholder="密码" v-model="param.password">
<template #prepend>
<el-icon>
<Lock />
@ -30,17 +26,9 @@
</el-input>
</el-form-item>
<div class="pwd-tips">
<el-checkbox
class="pwd-checkbox"
v-model="checked"
label="记住密码"
/>
<el-link type="primary" @click="$router.push('/reset-pwd')"
>忘记密码</el-link
>
<el-link type="primary" @click="toggleLoginType"
>验证码登录</el-link
>
<el-checkbox class="pwd-checkbox" v-model="checked" label="记住密码" />
<el-link type="primary" @click="$router.push('/reset-pwd')">忘记密码</el-link>
<el-link type="primary" @click="toggleLoginType">验证码登录</el-link>
</div>
</template>
<!-- 邮箱验证码登录表单切换后显示 -->
@ -49,7 +37,9 @@
<el-form-item prop="email">
<el-input v-model="code_login_form.email" placeholder="请输入邮箱">
<template #prepend>
<el-icon><Message /></el-icon>
<el-icon>
<Message />
</el-icon>
</template>
</el-input>
</el-form-item>
@ -57,15 +47,12 @@
<el-form-item prop="code">
<el-input v-model="code_login_form.code" placeholder="请输入验证码">
<template #prepend>
<el-icon><VerificationCode /></el-icon>
<el-icon>
<VerificationCode />
</el-icon>
</template>
<template #append>
<el-button
type="text"
:disabled="countdown > 0"
@click="sendCode"
class="code-btn"
>
<el-button type="text" :disabled="countdown > 0" @click="sendCode" class="code-btn">
{{ countdown > 0 ? `${countdown}s后重发` : "发送验证码" }}
</el-button>
</template>
@ -73,28 +60,19 @@
</el-form-item>
<!-- 切换链接无记住密码 -->
<div class="pwd-tips">
<el-link type="primary" @click="toggleLoginType"
>密码登录</el-link>
<el-link type="primary" @click="toggleLoginType">密码登录</el-link>
</div>
</template>
<el-button
class="login-btn"
type="primary"
size="large"
@click="onLogin"
>登录</el-button
>
<el-button class="login-btn" type="primary" size="large" @click="onLogin">登录</el-button>
<p class="login-text">
没有账号<el-link type="primary" @click="$router.push('/register')"
>立即注册</el-link
>
没有账号<el-link type="primary" @click="$router.push('/register')">立即注册</el-link>
</p>
</el-form>
<div>
<!-- 第三登录 -->
<el-divider content-position="center">第三方登录</el-divider>
<div class="login-text">
<el-row :gutter="12" >
<el-row :gutter="12">
<!-- thirdPartyLogoInfoList -->
<template v-for="(item, index) in thirdPartyLogoInfoList" :key="index">
<el-col :span="8">
@ -109,32 +87,41 @@
</div>
</div>
</div>
<el-dialog
v-model="second_auth_visible"
title="二次认证"
:close-on-click-modal="false"
width="30%"
>
<el-dialog v-model="second_auth_visible" title="二次认证" :close-on-click-modal="false" width="30%">
<!-- 输入验证码 -->
<span>请选择二次认证方式</span>
<el-select v-model="second_auth_method" placeholder="请选择认证方式" style="width: 100%; margin-top: 20px;">
<el-option v-for="item in second_auth_type_list" :key="item" :label="item.toUpperCase()" :value="item"></el-option>
<el-option v-for="item in second_auth_type_list" :key="item" :label="item.toUpperCase()"
:value="item"></el-option>
</el-select>
<el-row>
<span>输入验证码</span>
<el-input placeholder="请输入验证码" style="width: 100%; margin-top: 20px;" v-model="second_auth_code" >
<el-input placeholder="请输入验证码" style="width: 100%; margin-top: 20px;" v-model="second_auth_code" v-if="second_auth_method == 'TOTP'">
<template #prepend>
<el-icon>
<Lock />
</el-icon>
</template>
</el-input>
<el-input v-model="code_login_form.code" placeholder="请输入验证码" v-if="second_auth_method == 'EMAIL'">
<template #prepend>
<el-icon><VerificationCode /></el-icon>
</template>
<template #append>
<el-button
type="text"
:disabled="countdown > 0"
@click="sendCode"
class="code-btn"
>
{{ countdown > 0 ? `${countdown}s后重发` : "发送验证码" }}
</el-button>
</template>
</el-input>
</el-row>
<el-row>
<el-button class="login-btn"
type="primary"
size="large" @click="secondAuthLogin">确认</el-button>
<el-button class="login-btn" type="primary" size="large" @click="secondAuthLogin">确认</el-button>
</el-row>
</el-dialog>
@ -143,17 +130,18 @@
<script setup lang="ts">
import { ref, reactive, inject, onMounted, h } from "vue";
import { useRouter,useRoute } from "vue-router";
import { useRouter, useRoute } from "vue-router";
import { ElMessage } from "element-plus";
import { loginService } from "@/api/user";
import { GetUserInfoService } from "@/api/user";
import { UserToken } from "@/types/user";
import { getBrowserFingerprint,getStoredFingerprint } from "@/utils/fingerprint";
import { getBrowserFingerprint, getStoredFingerprint } from "@/utils/fingerprint";
import { usePermissStore } from "@/store/permiss";
import Cookies from 'js-cookie';
import {getThirdPartyUUID,getThirdPartyLoginStatus,getThirdPartyLoginUrl,sendLoginCode, loginByCode, secondAuthLoginService} from "@/api/user";
import { getThirdPartyUUID, getThirdPartyLoginStatus, getThirdPartyLoginUrl, sendLoginCode, loginByCode, secondAuthLoginService } from "@/api/user";
import { log, time } from "console";
import { pa } from "element-plus/es/locale";
import { state } from "mermaid/dist/rendering-util/rendering-elements/shapes/state";
//
const lgStr = localStorage.getItem("login-param");
const defParam = lgStr ? JSON.parse(lgStr) : null;
@ -179,7 +167,8 @@ const code_login_form = ref({
email: '',
code: '',
login_type: 1, // 1: 2:
fingerprint: '' //
fingerprint: '', //
state: '' // key
});
//
@ -206,11 +195,12 @@ const currentLoginRequest = ref(0); //当前请求次数
const timer = ref(null); //
const secondAuthLogin = async () => {
if(!second_auth_method.value){
if (!second_auth_method.value) {
ElMessage.error("请选择认证方式");
return;
}
if(!second_auth_code.value){
second_auth_code.value = second_auth_code.value ? second_auth_code.value.trim() : code_login_form.value.code;
if (!second_auth_code.value) {
ElMessage.error("请输入验证码");
return;
}
@ -232,7 +222,7 @@ const secondAuthLogin = async () => {
let now = new Date();
localStorage.setItem("end_time", (now.setDate(now.getHours())).toString()); //
await getMyUserInfo(userTokenInfo.user_id);
}else{
} else {
ElMessage.error(result["message"] || "登录失败!请稍后再试");
return;
}
@ -296,7 +286,7 @@ var loginData = ref({
ip: "",
fingerprint: '' //
});
const querySite =ref("");
const querySite = ref("");
const second_auth_state_key = ref(""); //key
const second_auth_method = ref("");
const second_auth_type_list = ref([]);
@ -307,18 +297,18 @@ onMounted(async () => {
param.fingerprint = fp.fingerprint;
loginData.value.fingerprint = fp.fingerprint;
code_login_form.value.fingerprint = fp.fingerprint;
console.log("fp",fp.fingerprint);
console.log("fp", fp.fingerprint);
// query
const queryParams = route.query;
console.log('Received query parameters:', queryParams);
if(queryParams.site){
if (queryParams.site) {
querySite.value = queryParams.site as string;
}
});
const thirdLogin = async (type) => {
//uuid
let uuidResp = await getThirdPartyUUID({"type": type});
let uuidResp = await getThirdPartyUUID({ "type": type });
if (uuidResp["code"] !== 0) {
ElMessage.error("获取UUID失败请稍后再试");
return;
@ -328,7 +318,7 @@ const thirdLogin = async (type) => {
ElMessage.error("获取UUID失败请稍后再试");
return;
}
let result={};
let result = {};
//
// for(let i = 0; i < thirdPartyLogoInfoList.value.length; i++) {
// if(thirdPartyLogoInfoList.value[i].name === type) {
@ -336,7 +326,7 @@ const thirdLogin = async (type) => {
// break;
// }
// }
result = await getThirdPartyLoginUrl({uuid: uuid,"type": "login", "platform": type,"fingerprint": param.fingerprint});
result = await getThirdPartyLoginUrl({ uuid: uuid, "type": "login", "platform": type, "fingerprint": param.fingerprint });
if (result["code"] !== 0) {
ElMessage.error("获取登录地址失败!请稍后再试");
return;
@ -358,27 +348,27 @@ const thirdLogin = async (type) => {
ElMessage.error("登录超时,请重新登录!");
return;
}
let statusResp = await getThirdPartyLoginStatus({uuid: uuid,host_id:loginData.value.fingerprint});
let statusResp = await getThirdPartyLoginStatus({ uuid: uuid, host_id: loginData.value.fingerprint });
if (statusResp["code"] !== 0) {
ElMessage.error("获取登录状态失败!请稍后再试");
clearInterval(timer.value);
return;
}
if(statusResp["code"] === 9){
if (statusResp["code"] === 9) {
ElMessage.error("服务器错误,请稍后再试!");
clearInterval(timer.value);
return;
}
if(statusResp["data"]["status"] === 163) {
if (statusResp["data"]["status"] === 163) {
ElMessage.error("该账号未绑定,请先绑定账号!");
clearInterval(timer.value);
return;
}
let status = statusResp["data"];
if(status["status"] === 0) {
if (status["status"] === 0) {
//
clearInterval(timer.value);
let userInfo:UserToken = status["user_info"];
let userInfo: UserToken = status["user_info"];
globalData["token"] = userInfo.access_token;
localStorage.setItem("token", userInfo.access_token);
localStorage.setItem("refresh_token", userInfo.refresh_token);
@ -411,15 +401,16 @@ const check_email = (email) => {
return emailReg.test(email);
}
const sendCode = async () => {
if (code_login_form.value.email === "" || check_email(code_login_form.value.email) === false) {
if ((code_login_form.value.email === "" || check_email(code_login_form.value.email) === false) && second_auth_state_key.value == "") {
ElMessage.error("请输入正确邮箱地址!");
return;
}
code_login_form.value.state = second_auth_state_key.value;
let result = await sendLoginCode(code_login_form.value);
if (result["code"] !== 0) {
ElMessage.error(result["message"] || "发送验证码失败!请稍后再试");
return;
}else{
} else {
countdown.value = 60;
timer.value = setInterval(() => {
countdown.value--;
@ -432,8 +423,8 @@ const sendCode = async () => {
}
const HandleLoginByCode =async () => {
if(code_login_form.value.email === "" || code_login_form.value.code === "" || check_email(code_login_form.value.email) === false){
const HandleLoginByCode = async () => {
if (code_login_form.value.email === "" || code_login_form.value.code === "" || check_email(code_login_form.value.email) === false) {
ElMessage.error("请输入邮箱和验证码!");
return;
}
@ -441,7 +432,7 @@ const HandleLoginByCode =async () => {
if (result["code"] !== 0) {
ElMessage.error(result["message"] || "登录失败!请稍后再试");
return;
}else{
} else {
let userTokenInfo: UserToken = result["data"];
globalData["token"] = result.data;
localStorage.setItem("token", userTokenInfo.access_token);
@ -455,9 +446,9 @@ const HandleLoginByCode =async () => {
};
const toggleLoginType = () => {
if(login_type.value === "pwd"){
if (login_type.value === "pwd") {
login_type.value = "code";
}else{
} else {
login_type.value = "pwd";
}
};
@ -475,7 +466,7 @@ const onLogin = async () => {
console.log("login result:", result);
if (result["code"] === 0) {
ElMessage.success("登录成功");
}else if (result["code"] == 1102) {
} else if (result["code"] == 1102) {
//
second_auth_visible.value = true;
second_auth_state_key.value = result["data"]["state"];
@ -487,10 +478,10 @@ const onLogin = async () => {
console.log("second_auth_type_list:", second_auth_type_list.value);
return;
}else if(result["code"] === 1101){
} else if (result["code"] === 1101) {
ElMessage.error(result["message"] || "该账号已被禁用,请联系管理员!");
return;
}else{
} else {
ElMessage.error(result["message"] || "登录失败!请稍后再试");
return;
}
@ -515,7 +506,7 @@ const getMyUserInfo = async (id) => {
id: id,
};
result = await GetUserInfoService(tokenData);
const cookie= Cookies.get("user_token");
const cookie = Cookies.get("user_token");
console.log("cookie:", cookie);
if (result["code"] === 0) {
//console.log("token data:",this.tokenData)
@ -534,9 +525,9 @@ const getMyUserInfo = async (id) => {
localStorage.setItem("ms_keys", JSON.stringify(keys));
localStorage.setItem("ms_role", result["data"]["Role"]);
if(querySite.value === "" ){
if (querySite.value === "") {
router.push("/");
}else{
} else {
localStorage.setItem("targetSite", querySite.value);
router.push({ path: `/project-select` });
}
@ -633,13 +624,16 @@ const getMyUserInfo = async (id) => {
.gitee-btn {
background-image: url(../../assets/img/gitee_logo.png) !important;
}
.qq-btn {
background-image: url(https://wiki.connect.qq.com/wp-content/uploads/2016/12/Connect_logo_4.png);
}
.google-btn {
background-image: url(../../assets/img/google-logo_resized.png) !important;
}
.code-btn :deep(.el-button--text:not(.is-disabled)):hover {
color: #0d66d0 !important;
background: rgba(22, 119, 255, 0.05) !important;

View File

@ -110,6 +110,20 @@
</div>
</el-tab-pane>
<el-tab-pane name="label6" label="二次认证" class="user-tabpane">
<br />
<el-switch v-model="second_auth.password_need_second_auth" active-text="打开" inactive-text="密码登录需进行二次认证" />
<br />
<br />
<el-switch v-model="second_auth.code_need_second_auth" active-text="打开" inactive-text="验证码登录需进行二次认证" />
<br />
<br />
<el-switch v-model="second_auth.third_party_need_second_auth" active-text="打开" inactive-text="第三方登录需进行二次认证" />
<br />
<br />
<el-switch v-model="second_auth.ai_second_auth" active-text="打开" inactive-text="智能增强认证" />
<br />
<el-button type="primary" @click="updateUserSecondAuthInfo(userInfo)">保存二次认证设置</el-button>
<div>
<p>TOTP密钥</p>
<el-button type="primary" @click="GenTOTPSecret" :disabled="gen_totp_secret">生成TOTP密钥</el-button>
@ -188,6 +202,13 @@ const totp_secret = ref('');
const totp_url = ref('');
const totp_secret_created_at = ref('');
const second_auth = ref({
password_need_second_auth: true,
code_need_second_auth: true,
third_party_need_second_auth: true,
ai_second_auth: true,
});
const thirdPartyUserInfo = ref<ThirdPartyUserInfo[]>([]);
const thirdPartyPlatform = ref([
@ -213,6 +234,10 @@ const unBindTOTPSecret =async () => {
let req = {
token: localStorage.getItem('token'),
};
//
if (!confirm("确定要解绑TOTP密钥吗解绑后将无法使用TOTP进行二次认证")) {
return;
}
try{
let result = await unBindTOTPSecretService(req);
if (result["code"] == 0) {
@ -307,7 +332,30 @@ const updateUserInfo = async (data: any) => {
password:data.Password,
email:data.Email,
avatar:data.Avatar,
role:data.Role
role:data.Role,
};
result = await updateUserInfoService(req)
if (result["code"] === 0) {
ElMessage.success("更新成功");
} else {
ElMessage.error("更新失败");
}
}catch(e){
console.log(e);
}
};
const updateUserSecondAuthInfo = async (data: any) => {
let result = null;
try{
let req={
token:localStorage.getItem("token"),
id:data.ID,
password_need_second_auth: second_auth.value.password_need_second_auth ? 1 : -1,
code_need_second_auth: second_auth.value.code_need_second_auth? 1 : -1,
third_party_need_second_auth: second_auth.value.third_party_need_second_auth ? 1 : -1,
ai_second_auth: second_auth.value.ai_second_auth ? 1 : -1,
};
result = await updateUserInfoService(req)
if (result["code"] === 0) {
@ -378,6 +426,10 @@ const GetMyUserInfo = async () => {
imgSrc.value = avatarImg.value;
userInfo.value = result.data;
userInfo.value.Password = '**********';
second_auth.value.password_need_second_auth = result.data.password_need_second_auth > 0 ? true : false;
second_auth.value.code_need_second_auth = result.data.code_need_second_auth > 0 ? true : false;
second_auth.value.third_party_need_second_auth = result.data.third_party_need_second_auth > 0 ? true : false;
second_auth.value.ai_second_auth = result.data.ai_second_auth > 0 ? true : false;
}else{
ElMessage.error(result["msg"]);
}
@ -578,7 +630,7 @@ const getThirdPartyUserInfo = async () => {
let result = await getThirdPartyInfo(req);
if (result["code"] == 0) {
//
console.log(result.data);
//console.log(result.data);
thirdPartyUserInfo.value = result.data;
}else{
ElMessage.error(result["msg"]);