添加二次认证开关

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; Gender: string;
Role: string; Role: string;
Avatar: 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-input>
</el-form-item> </el-form-item>
<el-form-item prop="password"> <el-form-item prop="password">
<el-input <el-input type="password" placeholder="密码" v-model="param.password">
type="password"
placeholder="密码"
v-model="param.password"
>
<template #prepend> <template #prepend>
<el-icon> <el-icon>
<Lock /> <Lock />
@ -30,17 +26,9 @@
</el-input> </el-input>
</el-form-item> </el-form-item>
<div class="pwd-tips"> <div class="pwd-tips">
<el-checkbox <el-checkbox class="pwd-checkbox" v-model="checked" label="记住密码" />
class="pwd-checkbox" <el-link type="primary" @click="$router.push('/reset-pwd')">忘记密码</el-link>
v-model="checked" <el-link type="primary" @click="toggleLoginType">验证码登录</el-link>
label="记住密码"
/>
<el-link type="primary" @click="$router.push('/reset-pwd')"
>忘记密码</el-link
>
<el-link type="primary" @click="toggleLoginType"
>验证码登录</el-link
>
</div> </div>
</template> </template>
<!-- 邮箱验证码登录表单切换后显示 --> <!-- 邮箱验证码登录表单切换后显示 -->
@ -49,7 +37,9 @@
<el-form-item prop="email"> <el-form-item prop="email">
<el-input v-model="code_login_form.email" placeholder="请输入邮箱"> <el-input v-model="code_login_form.email" placeholder="请输入邮箱">
<template #prepend> <template #prepend>
<el-icon><Message /></el-icon> <el-icon>
<Message />
</el-icon>
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
@ -57,15 +47,12 @@
<el-form-item prop="code"> <el-form-item prop="code">
<el-input v-model="code_login_form.code" placeholder="请输入验证码"> <el-input v-model="code_login_form.code" placeholder="请输入验证码">
<template #prepend> <template #prepend>
<el-icon><VerificationCode /></el-icon> <el-icon>
<VerificationCode />
</el-icon>
</template> </template>
<template #append> <template #append>
<el-button <el-button type="text" :disabled="countdown > 0" @click="sendCode" class="code-btn">
type="text"
:disabled="countdown > 0"
@click="sendCode"
class="code-btn"
>
{{ countdown > 0 ? `${countdown}s后重发` : "发送验证码" }} {{ countdown > 0 ? `${countdown}s后重发` : "发送验证码" }}
</el-button> </el-button>
</template> </template>
@ -73,21 +60,12 @@
</el-form-item> </el-form-item>
<!-- 切换链接无记住密码 --> <!-- 切换链接无记住密码 -->
<div class="pwd-tips"> <div class="pwd-tips">
<el-link type="primary" @click="toggleLoginType" <el-link type="primary" @click="toggleLoginType">密码登录</el-link>
>密码登录</el-link>
</div> </div>
</template> </template>
<el-button <el-button class="login-btn" type="primary" size="large" @click="onLogin">登录</el-button>
class="login-btn"
type="primary"
size="large"
@click="onLogin"
>登录</el-button
>
<p class="login-text"> <p class="login-text">
没有账号<el-link type="primary" @click="$router.push('/register')" 没有账号<el-link type="primary" @click="$router.push('/register')">立即注册</el-link>
>立即注册</el-link
>
</p> </p>
</el-form> </el-form>
<div> <div>
@ -109,32 +87,41 @@
</div> </div>
</div> </div>
</div> </div>
<el-dialog <el-dialog v-model="second_auth_visible" title="二次认证" :close-on-click-modal="false" width="30%">
v-model="second_auth_visible"
title="二次认证"
:close-on-click-modal="false"
width="30%"
>
<!-- 输入验证码 --> <!-- 输入验证码 -->
<span>请选择二次认证方式</span> <span>请选择二次认证方式</span>
<el-select v-model="second_auth_method" placeholder="请选择认证方式" style="width: 100%; margin-top: 20px;"> <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-select>
<el-row> <el-row>
<span>输入验证码</span> <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> <template #prepend>
<el-icon> <el-icon>
<Lock /> <Lock />
</el-icon> </el-icon>
</template> </template>
</el-input> </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-row> <el-row>
<el-button class="login-btn" <el-button class="login-btn" type="primary" size="large" @click="secondAuthLogin">确认</el-button>
type="primary"
size="large" @click="secondAuthLogin">确认</el-button>
</el-row> </el-row>
</el-dialog> </el-dialog>
@ -154,6 +141,7 @@ 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 { log, time } from "console";
import { pa } from "element-plus/es/locale"; 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 lgStr = localStorage.getItem("login-param");
const defParam = lgStr ? JSON.parse(lgStr) : null; const defParam = lgStr ? JSON.parse(lgStr) : null;
@ -179,7 +167,8 @@ const code_login_form = ref({
email: '', email: '',
code: '', code: '',
login_type: 1, // 1: 2: login_type: 1, // 1: 2:
fingerprint: '' // fingerprint: '', //
state: '' // key
}); });
// //
@ -210,6 +199,7 @@ const secondAuthLogin = async () => {
ElMessage.error("请选择认证方式"); ElMessage.error("请选择认证方式");
return; return;
} }
second_auth_code.value = second_auth_code.value ? second_auth_code.value.trim() : code_login_form.value.code;
if (!second_auth_code.value) { if (!second_auth_code.value) {
ElMessage.error("请输入验证码"); ElMessage.error("请输入验证码");
return; return;
@ -411,10 +401,11 @@ const check_email = (email) => {
return emailReg.test(email); return emailReg.test(email);
} }
const sendCode = async () => { 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("请输入正确邮箱地址!"); ElMessage.error("请输入正确邮箱地址!");
return; return;
} }
code_login_form.value.state = second_auth_state_key.value;
let result = await sendLoginCode(code_login_form.value); let result = await sendLoginCode(code_login_form.value);
if (result["code"] !== 0) { if (result["code"] !== 0) {
ElMessage.error(result["message"] || "发送验证码失败!请稍后再试"); ElMessage.error(result["message"] || "发送验证码失败!请稍后再试");
@ -633,13 +624,16 @@ const getMyUserInfo = async (id) => {
.gitee-btn { .gitee-btn {
background-image: url(../../assets/img/gitee_logo.png) !important; background-image: url(../../assets/img/gitee_logo.png) !important;
} }
.qq-btn { .qq-btn {
background-image: url(https://wiki.connect.qq.com/wp-content/uploads/2016/12/Connect_logo_4.png); background-image: url(https://wiki.connect.qq.com/wp-content/uploads/2016/12/Connect_logo_4.png);
} }
.google-btn { .google-btn {
background-image: url(../../assets/img/google-logo_resized.png) !important; background-image: url(../../assets/img/google-logo_resized.png) !important;
} }
.code-btn :deep(.el-button--text:not(.is-disabled)):hover { .code-btn :deep(.el-button--text:not(.is-disabled)):hover {
color: #0d66d0 !important; color: #0d66d0 !important;
background: rgba(22, 119, 255, 0.05) !important; background: rgba(22, 119, 255, 0.05) !important;

View File

@ -110,6 +110,20 @@
</div> </div>
</el-tab-pane> </el-tab-pane>
<el-tab-pane name="label6" label="二次认证" class="user-tabpane"> <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> <div>
<p>TOTP密钥</p> <p>TOTP密钥</p>
<el-button type="primary" @click="GenTOTPSecret" :disabled="gen_totp_secret">生成TOTP密钥</el-button> <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_url = ref('');
const totp_secret_created_at = 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 thirdPartyUserInfo = ref<ThirdPartyUserInfo[]>([]);
const thirdPartyPlatform = ref([ const thirdPartyPlatform = ref([
@ -213,6 +234,10 @@ const unBindTOTPSecret =async () => {
let req = { let req = {
token: localStorage.getItem('token'), token: localStorage.getItem('token'),
}; };
//
if (!confirm("确定要解绑TOTP密钥吗解绑后将无法使用TOTP进行二次认证")) {
return;
}
try{ try{
let result = await unBindTOTPSecretService(req); let result = await unBindTOTPSecretService(req);
if (result["code"] == 0) { if (result["code"] == 0) {
@ -307,7 +332,30 @@ const updateUserInfo = async (data: any) => {
password:data.Password, password:data.Password,
email:data.Email, email:data.Email,
avatar:data.Avatar, 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) result = await updateUserInfoService(req)
if (result["code"] === 0) { if (result["code"] === 0) {
@ -378,6 +426,10 @@ const GetMyUserInfo = async () => {
imgSrc.value = avatarImg.value; imgSrc.value = avatarImg.value;
userInfo.value = result.data; userInfo.value = result.data;
userInfo.value.Password = '**********'; 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{ }else{
ElMessage.error(result["msg"]); ElMessage.error(result["msg"]);
} }
@ -578,7 +630,7 @@ const getThirdPartyUserInfo = async () => {
let result = await getThirdPartyInfo(req); let result = await getThirdPartyInfo(req);
if (result["code"] == 0) { if (result["code"] == 0) {
// //
console.log(result.data); //console.log(result.data);
thirdPartyUserInfo.value = result.data; thirdPartyUserInfo.value = result.data;
}else{ }else{
ElMessage.error(result["msg"]); ElMessage.error(result["msg"]);